import csv
import os
import io
import json
import hashlib
from datetime import datetime, timedelta, date, time
import traceback
import re
import pandas as pd
from functools import wraps
from urllib.parse import quote, unquote
from typing import List, Dict, Tuple, Optional
from docx import Document
from docx.shared import Pt
from werkzeug.utils import secure_filename
from collections import Counter
import shutil
from flask import Flask, flash, jsonify, request, render_template_string, session, redirect, url_for, abort, send_file, \
    get_flashed_messages, send_from_directory
import zipfile
import threading
import uuid
from urllib.parse import unquote

# Per-File-Lock-Registry: jede Datei bekommt ihren eigenen Lock.
# Verhindert, dass ein Hardware-Save einen Facility-Save blockiert
# und umgekehrt. _file_locks_registry schützt nur das Dict selbst.
_file_locks: dict = {}
_file_locks_registry = threading.Lock()


def _get_file_lock(filepath: str) -> threading.Lock:
    """Gibt den Lock für einen bestimmten Dateipfad zurück (lazy-init)."""
    with _file_locks_registry:
        if filepath not in _file_locks:
            _file_locks[filepath] = threading.Lock()
        return _file_locks[filepath]

# 1. Flask App Instanz erstellen
app = Flask(__name__, static_folder='db/static')
app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'dein-sehr-geheimer-schluessel-fuer-flask-sessions')

# --- Page-View-Tracking (in-memory, kein I/O auf dem Request-Pfad) ---
from collections import defaultdict
import time as _time

page_counter_total = Counter()
page_counter_weekly = defaultdict(Counter)   # { "2026-W14": Counter({"/foo": 3, ...}) }
weekly_active_users = defaultdict(set)       # { "2026-W14": {"user:alice", "ip:10.0.0.5", ...} }
user_traffic_total = Counter()               # { "user:alice": 142, "ip:10.0.0.5": 23 }
user_traffic_weekly = defaultdict(Counter)   # { "2026-W14": Counter({"user:alice": 45, ...}) }

def _get_visitor_key():
    """Gibt user:<id> für eingeloggte Nutzer zurück, sonst ip:<adresse>."""
    user_id = session.get('user_id')
    if user_id:
        return f"user:{user_id}"
    forwarded = request.headers.get('X-Forwarded-For')
    ip = forwarded.split(',')[0].strip() if forwarded else request.remote_addr
    return f"ip:{ip}"

@app.before_request
def track_page_view():
    if request.method == 'GET' and not request.path.startswith(('/static', '/api/')):
        week_key = datetime.now().strftime('%Y-W%V')
        page_counter_total[request.path] += 1
        page_counter_weekly[week_key][request.path] += 1
        visitor_key = _get_visitor_key()
        weekly_active_users[week_key].add(visitor_key)
        user_traffic_total[visitor_key] += 1
        user_traffic_weekly[week_key][visitor_key] += 1

def _flush_analytics():
    """Background-Thread: schreibt Analytics alle 5 Minuten auf Disk."""
    while True:
        _time.sleep(300)
        try:
            data = {
                "total": dict(page_counter_total.most_common(100)),
                "by_week": {
                    week: dict(counter.most_common(100))
                    for week, counter in sorted(page_counter_weekly.items())
                },
                "unique_visitors_by_week": {
                    week: list(visitors)
                    for week, visitors in sorted(weekly_active_users.items())
                },
                "user_traffic": {
                    "total": dict(user_traffic_total.most_common(50)),
                    "by_week": {
                        week: dict(counter.most_common(50))
                        for week, counter in sorted(user_traffic_weekly.items())
                    }
                }
            }
            with _get_file_lock(ANALYTICS_FILE):
                os.makedirs(ANALYTICS_DIR, exist_ok=True)
                with open(ANALYTICS_FILE, 'w', encoding='utf-8') as f:
                    json.dump(data, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"ANALYTICS Flush-Fehler: {e}")

# Globale Variablen
EMPLOYEES_FILE_PATH = 'db/MBET_employees.csv'
ATTENDANCE_DIR = 'db/attendance'
ATTENDANCE_DATA_FILE = 'db/attendance/attendance_data.json'
ATTENDANCE_GROUPS_FILE = 'db/attendance/groups.json'
ATTENDANCE_QUOTAS_FILE = 'db/attendance/vacation_quotas.json'
ATTENDANCE_CLOSURES_FILE = 'db/attendance/company_closures.json'
QUALIFICATIONS_FILE_PATH = 'db/MBET_quali_display.csv'
USERS_FILE_PATH = 'db/MBET_user_display.csv'
ASSIGNED_EMPLOYEES_CSV = 'db/Pruefst4_assigned_employees.csv'
ASSIGNMENT_RULES_FILE_PATH = 'db/Pruefst4_assignment_rules.csv'
COMMENTS_FILE_PATH = 'db/MBET_comments.csv'
CURRENT_PROJECT_FILE_PATH = 'db/Pruefst4_timeline_projects.csv'
TIMELINE_PROJECTS_DISPLAY_CSV = 'db/timeline_projects_display.csv'
PROCESS_TEMPLATES_FILE_PATH = 'db/process/process_templates.json'
PROCESS_TEMPLATE_IMAGES_DIR = 'db/process/template_images'
MANUAL_CATALOG_FILE_PATH = 'db/process/manual_catalog.json'
PROJECT_MANUALS_DIR = 'db/process/manuals'
DOCUMENTS_FOLDER_PATH = 'db/documents'
CERTIFICATES_FOLDER_PATH = 'db/certificates'
CERTIFICATES_UPLOADS_FOLDER_PATH = os.path.join(CERTIFICATES_FOLDER_PATH, 'uploads')
LOGGED_IN_USER_DISPLAY_NAME_FOR_PYTHON = "System"
ALL_USERS = []
ALL_QUALIFICATIONS = []
ALL_EMPLOYEES = []
MERKMALE_IN_ORDER = []  # Wird von load_qualifications gefüllt
USER_CSV_FIELDNAMES = ['NACHNAME', 'VORNAME', 'ID', 'LEVEL', 'PASSWORD', 'ADMIN', 'ROLES', 'UNITS', 'IS_EMPLOYEE']
DEFAULT_PAST_WEEKS_PRESENCE = 0
DEFAULT_FUTURE_WEEKS_PRESENCE = 1
ALL_PROJECTS = []
ALL_COMMENTS = []
ASSIGNED_EMPLOYEES_FIELDNAMES = ['project_code', 'engine_serial', 'event_title', 'date_str', 'shift', 'employee_id',
                                 'engine_type']
ASSIGNMENT_RULES_FIELDNAMES = ['rule_id', 'event_titles', 'engine_types', 'required_skills']
VALID_ASSIGNABLE_STATUSES = {'X', 'TS', 'TT'}
LOCATIONS_FILE_PATH = 'db/MBET_Locations.csv'
LOCATIONS_EVENTS_CSV_PATH = 'db/MBET_locations_events.csv'  # NEUE Globale Variable
ALL_LOCATIONS = []
DB_ROOT_PATH = 'db'
ORDER_DATA_DIR = "db/ordersystem"
ORDER_ORDERS_FILE = os.path.join(ORDER_DATA_DIR, "order_order.json")
ORDER_TEMPLATES_FILE = os.path.join(ORDER_DATA_DIR, "order_templates.json")

PROJECT_STATUS_DIR = "db/projectstatus"
DEVIATION_ATTACHMENTS_DIR = "db/projectstatus/attachments"
TEST_EQUIPMENT_DATA_FILE = 'db/hardware/test_equipment_data.json'
COMPLETENESS_LOG_FILE = 'db/hardware/completeness_log.json'
COMPLETENESS_CONFIG_FILE = 'db/hardware/completeness_config.json'
HARDWARE_UPLOADS_DIR = 'db/hardware/uploads'

TESTCELL_HISTORY_FILE = os.path.join(DB_ROOT_PATH, 'testcell_history.json')

# --- MAINTENANCE  ---
MAINTENANCE_DATA_DIR = os.path.join(DB_ROOT_PATH, 'maintenance')
MAINTENANCE_ORDERS_FILE = os.path.join(MAINTENANCE_DATA_DIR, 'maintenance_orders.json')
MAINTENANCE_DEFINITIONS_FILE = os.path.join(MAINTENANCE_DATA_DIR, 'maintenance_definitions.json')
MAINTENANCE_COMPANIES_FILE = os.path.join(MAINTENANCE_DATA_DIR, 'maintenance_companies.json')
MAINTENANCE_UPLOADS_DIR = os.path.join(MAINTENANCE_DATA_DIR, 'uploads')
MAINTENANCE_LOGBOOK_UPLOADS_DIR = os.path.join(MAINTENANCE_DATA_DIR, 'logbook_uploads')
MAINTENANCE_LOGBOOK_FILE = os.path.join(MAINTENANCE_DATA_DIR, 'maintenance_logbook.json')

# --- ASSIGNMENTs  ---
GLOBAL_ASSIGNMENTS_FILE = os.path.join(DB_ROOT_PATH, 'global_assignments.json')
ASSIGNMENT_HISTORY_FILE = os.path.join(DB_ROOT_PATH, 'assignment_history.json')

# --- TRAINING KONSTANTEN ---
TRAINING_ROOT_DIR = os.path.join(DB_ROOT_PATH, 'training')
TRAINING_DATA_FILE = os.path.join(TRAINING_ROOT_DIR, 'trainings.json')
USER_RELATIONS_FILE = os.path.join(TRAINING_ROOT_DIR, 'user_relations.json')
TRAINING_UPLOADS_DIR = os.path.join(TRAINING_ROOT_DIR, 'uploads')

# 1a. Zentrale Definition der Rollen als Konstanten
class Roles:
    ADMIN = "ADMIN"
    OBSERVER = "OBSERVER"
    PLANNER = "PLANNER"
    TESTCELLINSPECTOR = "TESTCELLINSPECTOR"
    TESTMECHANIC = "TESTMECHANIC"
    TESTENGINEER = "TESTENGINEER"
    MAINTENANCEMANAGER = "MAINTENANCEMANAGER"
    HARDWAREMANAGER = "HARDWAREMANAGER"
    CONSUMABLEMANAGER = "CONSUMABLEMANAGER"
    TOPIC_ELEKTRIK = "TOPIC_ELEKTRIK"
    TOPIC_MECHANIK = "TOPIC_MECHANIK"
    TOPIC_IT = "TOPIC_IT"
    TOPIC_FACILITY = "TOPIC_FACILITY"
    TOPIC_TESTENG = "TOPIC_TESTENG"

# 1b.  Beschreibungen für Verwaltungsseite
AVAILABLE_ROLES = {
    Roles.ADMIN: "Vollzugriff.",
    Roles.OBSERVER: "Reiner Lesezugriff.",
    Roles.PLANNER: "Darf Timelines planen und Projekte bearbeiten.",
    Roles.TESTCELLINSPECTOR: "Darf als 'Vier-Augen-Prinzip' abgeschlossene Aufgaben prüfen und freigeben.",
    Roles.TESTMECHANIC: "Darf TFS und den Status von Wartungsaufgaben und Checklisten ändern.",
    Roles.TESTENGINEER: "Darf Testzellen, Rüstwagen und dazugehörige Prozesse verwalten.",
    Roles.MAINTENANCEMANAGER: "Darf Wartungspläne definieren und bearbeiten.",
    Roles.HARDWAREMANAGER: "Darf das Hardware-Inventar verwalten.",
    Roles.CONSUMABLEMANAGER: "Verwaltet das Bestellsystem für Verbrauchsmaterialien.",
    Roles.TOPIC_ELEKTRIK: "Darf Wartungen im Bereich Elektrik/Messtechnik freigeben.",
    Roles.TOPIC_MECHANIK: "Darf Wartungen im Bereich Anlagen/Mechanik freigeben.",
    Roles.TOPIC_IT: "Darf Wartungen im Bereich IT/Software freigeben.",
    Roles.TOPIC_FACILITY: "Darf Wartungen im Bereich Facility Management freigeben.",
    Roles.TOPIC_TESTENG: "Darf Wartungen im Bereich Test Engineering freigeben."
}

# --- 3D ANLAGENÜBERSICHT ---
FACILITY_DATA_DIR = os.path.join(DB_ROOT_PATH, 'facility')
FACILITY_STRUCTURE_FILE = os.path.join(FACILITY_DATA_DIR, 'structure.json')
FACILITY_MODELS_DIR = FACILITY_DATA_DIR
FACILITY_BACKUP_DIR = os.path.join(FACILITY_DATA_DIR, 'backups')
FACILITY_MODEL_FILENAME = 'anlage_gesamt_test.glb'
FACILITY_MODEL_PATH = os.path.join(FACILITY_MODELS_DIR, FACILITY_MODEL_FILENAME)
FACILITY_WIKI_FILE = os.path.join(FACILITY_DATA_DIR, 'wiki.json')
FACILITY_WIKI_HISTORY_FILE = os.path.join(FACILITY_DATA_DIR, 'wiki_history.json')
FACILITY_ATTACHMENTS_DIR = os.path.join(FACILITY_DATA_DIR, 'attachments')
FACILITY_WIKI_HISTORY_MAX = 15

# Standard-Daten für neue Struktur
FACILITY_EMPTY_DATA = {
    "betriebseinheiten": [],
    "gebaeude": [],
    "fluesse": [],
    "custom_layers": []
}

# Simulierte Live-Daten für die Status-Anzeige
ROOM_STATUS_DATA = {
}

# --- SFM TEXTS CONFIG ---
SFM_TEXTS_FILE = os.path.join(DB_ROOT_PATH, 'SFM_texts.json')
SFM_CALENDAR_FILE = os.path.join(DB_ROOT_PATH, 'sfm_calendar.json')

# --- ANALYTICS ---
ANALYTICS_DIR = os.path.join(DB_ROOT_PATH, 'analytics')
ANALYTICS_FILE = os.path.join(ANALYTICS_DIR, 'page_views.json')

# Standard-Werte, falls Datei nicht existiert
DEFAULT_SFM_TEXTS = {
    "motd": {
        "text": "Willkommen im Shop Floor Management System.",
        "type": "info",    # info, warning, danger, success
        "visible": True
    },
    "people_groups_filter": [],  # Leere Liste = alle Gruppen werden gezählt
    "efficiency_data": {},       # { "YYYY-MM-DD": { "IGT": 87, "CFX": 91, "PWC": 78, "Gesamt": 85 } }
    "efficiency_target": 85      # Zielwert in % für die Annotations-Linie
}

def load_app_config():
    """Lädt die zentrale Konfigurationsdatei config.json."""
    config_path = os.path.join(DB_ROOT_PATH, 'config.json')
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError) as e:
        print(f"!!! KRITISCHER FEHLER: Konfigurationsdatei '{config_path}' konnte nicht geladen werden: {e}")
        return {"timelines": [], "sap_filters": {}}


def save_app_config(config):
    """Speichert config.json thread-safe."""
    config_path = os.path.join(DB_ROOT_PATH, 'config.json')
    with _get_file_lock(config_path):
        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(config, f, ensure_ascii=False, indent=2)


def validate_group_references():
    """Startup-Check: warnt wenn config.json Gruppen-IDs referenziert die nicht existieren."""
    known_ids = {g['id'] for g in load_attendance_groups()}
    config = load_app_config()
    for tl in config.get('timelines', []):
        for gid in tl.get('assignment_groups', []):
            if gid not in known_ids:
                print(f"WARNUNG: Timeline '{tl.get('id')}' referenziert unbekannte Gruppe '{gid}' in assignment_groups")


def load_sfm_texts():
    """Lädt die konfigurierbaren Texte."""
    if not os.path.exists(SFM_TEXTS_FILE):
        return DEFAULT_SFM_TEXTS.copy()

    try:
        with open(SFM_TEXTS_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"Fehler beim Laden von SFM_texts.json: {e}")
        return DEFAULT_SFM_TEXTS.copy()


def save_sfm_texts_data(data):
    """Speichert die Texte atomar."""
    return save_json_file(SFM_TEXTS_FILE, data)


# 2. HTML-Konstanten und andere globale Konfigurationen
LOGIN_PAGE_HTML = """
<!DOCTYPE html>
<html lang="de"><head>
<meta charset="utf-8"/>
<link crossorigin="" href="https://fonts.gstatic.com/" rel="preconnect"/>
<link as="style" href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900&family=Space+Grotesk%3Awght%40400%3B500%3B700" onload="this.rel='stylesheet'" rel="stylesheet"/>
<title>Engine Test Facility - Login</title>
<link href="data:image/x-icon;base64," rel="icon" type="image/x-icon"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<style type="text/tailwindcss">
    @layer base {
      :root {
        --brand-primary-color: #0c7ff2;
        --brand-secondary-color: #1f2937;
        --text-primary-color: #ffffff;
        --text-secondary-color: #9ca3af;
        --surface-primary-color: #111418;
        --surface-secondary-color: #1f2937;
        --border-primary-color: #283039;
      }
    }
    body {
        background-color: var(--surface-primary-color);
        color: var(--text-primary-color);
        font-family: "Space Grotesk", "Noto Sans", sans-serif;
    }
    .form-input::placeholder {
      color: var(--text-secondary-color);
    }
    .login-button {
        background-color: var(--brand-primary-color);
    }
    .login-button:hover {
        background-color: #0a6cce;
    }
  </style>
</head>
<body>
<div class="relative flex size-full min-h-screen flex-col items-center justify-center overflow-x-hidden p-4">
<div class="w-full max-w-md">
<header class="mb-8 text-center">
<div class="inline-flex items-center gap-3 mb-4">
<div class="size-8 text-[var(--brand-primary-color)]">
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M24 4H6V17.3333V30.6667H24V44H42V30.6667V17.3333H24V4Z" fill="currentColor" fill-rule="evenodd"></path>
</svg>
</div>
<h1 class="text-3xl font-bold tracking-tight">Engine Test Facility</h1>
</div>
<p class="text-[var(--text-secondary-color)] text-lg">Please enter your credentials to continue.</p>
</header>
<main class="bg-[var(--surface-secondary-color)] shadow-2xl rounded-xl p-8">
<h2 class="text-2xl font-semibold text-center mb-6">Login</h2>
{% if error %}
    <p class="text-red-500 text-center mb-4 text-sm">{{ error }}</p>
{% endif %}
<form class="space-y-6" method="POST" action="{{ url_for('login_page') }}">
<div>
    <label class="sr-only" for="username">Benutzer-ID</label>
    <div class="relative">
    <input class="form-input block w-full rounded-lg border-0 bg-[#283039] py-3.5 pr-4 pl-10 text-[var(--text-primary-color)] shadow-sm ring-1 ring-inset ring-[var(--border-primary-color)] focus:ring-2 focus:ring-inset focus:ring-[var(--brand-primary-color)] sm:text-sm sm:leading-6 h-14 placeholder:text-[var(--text-secondary-color)]"
           id="username" name="username" placeholder="Benutzer-ID (z.B. YJ12345)" type="text" value="{{ request.form.username if request and request.form else '' }}"/>
    <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5 text-[var(--text-secondary-color)]">
            <path d="M10 8a3 3 0 100-6 3 3 0 000 6ZM3.465 14.493a1.23 1.23 0 00.41 1.412A9.957 9.957 0 0010 18c2.31 0 4.438-.784 6.131-2.1.43-.333.604-.903.408-1.41a7.002 7.002 0 00-13.074.003Z" />
        </svg>
    </div>
    </div>
</div>
<div>
<label class="sr-only" for="password">Password</label>
<div class="relative">
<input class="form-input block w-full rounded-lg border-0 bg-[#283039] py-3.5 pr-4 pl-10 text-[var(--text-primary-color)] shadow-sm ring-1 ring-inset ring-[var(--border-primary-color)] focus:ring-2 focus:ring-inset focus:ring-[var(--brand-primary-color)] sm:text-sm sm:leading-6 h-14 placeholder:text-[var(--text-secondary-color)]"
       id="password" name="password" placeholder="Passwort" type="password"/>
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg aria-hidden="true" class="h-5 w-5 text-[var(--text-secondary-color)]" fill="currentColor" viewBox="0 0 20 20">
<path clip-rule="evenodd" d="M10 1a4.5 4.5 0 00-4.5 4.5V9H5a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002 2v-7a2 2 0 00-2-2h-.5V5.5A4.5 4.5 0 0010 1zm3 8V5.5a3 3 0 10-6 0V9h6z" fill-rule="evenodd"></path>
</svg>
</div>
</div>
</div>
<div>
<button class="login-button flex w-full justify-center rounded-lg px-3 py-3 text-sm font-semibold leading-6 text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--brand-primary-color)] transition-colors duration-150" type="submit">
              Anmelden
            </button>
</div>
</form>
</main>
<footer class="mt-10 text-center text-sm text-[var(--text-secondary-color)]">
<p>© 2025 MTU Maintenance Test Facility. All rights reserved.</p>
</footer>
</div>
</div>
</body></html>
"""

DASHBOARD_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        body { font-family: 'Inter', sans-serif; background-color: #FFFFFF; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        .cover-area { height: 20vh; background-color: #f1f5f9; }
        .event-card { min-width: 280px; }
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
    </style>
</head>
<body>
<div class="flex h-screen bg-white">
    {% include 'sidebar.html' %}
    <main class="flex-1 overflow-y-auto">
        <div class="cover-area"></div>
        <div class="px-8 md:px-12 lg:px-16">
            <div class="relative h-12">
                <div class="absolute -top-10 left-0">
                    <div class="h-24 w-24 bg-white rounded-full flex items-center justify-center text-3xl font-bold text-slate-700 shadow-lg ring-8 ring-white">
                         {{ current_user_initials }}
                    </div>
                </div>
            </div>
            <h1 class="text-4xl font-bold text-slate-800 mt-8 mb-12">Maintenance & Test Facility</h1>
<!-- Persönliche Begrüßung & Aufgabe -->
            <section class="mb-10 grid grid-cols-1 lg:grid-cols-3 gap-6">
                <!-- Mein Plan (Clean) -->
                <div class="lg:col-span-2 bg-white rounded-lg border border-slate-200 shadow-sm p-6 flex flex-col justify-center relative overflow-hidden">
                    <div class="absolute left-0 top-0 bottom-0 w-1 {% if my_missions %}bg-blue-500{% else %}bg-slate-300{% endif %}"></div>
                    
                    <div class="flex items-center gap-4 mb-3">
                        <div class="h-10 w-10 rounded-full flex items-center justify-center flex-shrink-0 {% if my_missions %}bg-blue-50 text-blue-600{% else %}bg-slate-100 text-slate-400{% endif %}">
                            <span class="material-icons-outlined text-xl">
                                {% if my_missions %}engineering{% else %}free_breakfast{% endif %}
                            </span>
                        </div>
                        <h2 class="text-lg font-bold text-slate-800">Hallo, {{ current_user_display_name }}!</h2>
                    </div>

                    <div class="pl-[3.5rem]">
                        {% if my_missions %}
                            <div class="space-y-3"> <!-- Etwas engerer Abstand -->
                            {% for mission in my_missions %}
                                <div class="group">
                                    <div class="flex items-center gap-2 text-slate-800">
                                        <span class="text-base font-bold">{{ mission.task }}</span>
                                        <span class="text-slate-400 text-sm font-light">an</span>
                                        <span class="text-sm font-medium">{{ mission.engine }}</span>
                                    </div>
                                    <div class="flex items-center gap-3 text-xs text-slate-500 mt-0.5">
                                        <span class="bg-slate-100 px-1.5 py-0.5 rounded text-slate-600 font-mono">{{ mission.project }}</span>
                                        {% if mission.team %}
                                            <span class="flex items-center gap-1">
                                                <span class="text-slate-300">+</span> {{ mission.team|join(', ') }}
                                            </span>
                                        {% endif %}
                                    </div>
                                </div>
                            {% endfor %}
                            </div>
                        {% else %}
                            <p class="text-slate-500 text-sm">Für heute liegen keine Zuweisungen vor.</p>
                        {% endif %}
                                                <!-- NEU: ENTSCHEIDUNGEN -->
                        {% if my_decisions %}
                            <div class="mt-6 pt-4 border-t border-slate-100">
                                <h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3">
                                    Ausstehende Entscheidungen
                                    <span class="ml-1 px-1.5 py-0.5 rounded bg-slate-100 text-slate-500 font-bold text-xs">{{ my_decisions | length }}</span>
                                </h3>
                                <div class="space-y-2" id="decisionsListContainer">
                                    {% for dec in my_decisions %}
                                        <a href="{{ url_for('serve_maintenance_page', openId=dec.id) }}" class="decision-item flex items-center gap-3 p-2 rounded-md hover:bg-slate-50 transition-colors group" data-index="{{ loop.index0 }}">
                                            {% if dec.type == 'triage' %}
                                                <div class="w-2 h-2 rounded-full bg-orange-500 flex-shrink-0"></div>
                                                <span class="text-sm font-medium text-slate-700 group-hover:text-blue-600">Bewertung: {{ dec.title }}</span>
                                            {% elif dec.type == 'planning' %}
                                                <div class="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0"></div>
                                                <span class="text-sm font-medium text-slate-700 group-hover:text-blue-600">Maßnahme definieren: {{ dec.title }}</span>
                                            {% elif dec.type == 'approval' %}
                                                <div class="w-2 h-2 rounded-full bg-orange-400 flex-shrink-0"></div>
                                                <span class="text-sm font-medium text-slate-700 group-hover:text-blue-600">Freigabe Maßnahme: {{ dec.title }}</span>
                                            {% else %}
                                                <div class="w-2 h-2 rounded-full bg-purple-500 flex-shrink-0"></div>
                                                <span class="text-sm font-medium text-slate-700 group-hover:text-blue-600">Freigabe: {{ dec.title }}</span>
                                            {% endif %}
                                            {% if dec.date %}
                                                {% set parts = dec.date.split('-') %}
                                                <span class="text-[10px] text-slate-400 flex-shrink-0 ml-1">{{ parts[2] }}.{{ parts[1] }}.{{ parts[0] }}</span>
                                            {% endif %}
                                            <span class="material-icons-outlined text-slate-300 text-sm ml-auto opacity-0 group-hover:opacity-100">arrow_forward</span>
                                        </a>
                                    {% endfor %}
                                </div>
                                {% if my_decisions | length > 3 %}
                                <button id="decisionsToggleBtn" onclick="toggleDecisions()" class="mt-2 text-xs text-slate-400 hover:text-blue-500 transition-colors flex items-center gap-1">
                                    <span class="material-icons-outlined text-sm" id="decisionsToggleIcon">expand_more</span>
                                    <span id="decisionsToggleLabel">+ {{ (my_decisions | length) - 3 }} weitere anzeigen</span>
                                </button>
                                <script>
                                    (function() {
                                        const SHOW = 3;
                                        let expanded = false;
                                        function applyVisibility() {
                                            document.querySelectorAll('#decisionsListContainer .decision-item').forEach(function(el) {
                                                const idx = parseInt(el.dataset.index, 10);
                                                el.style.display = (expanded || idx < SHOW) ? '' : 'none';
                                            });
                                            const hidden = document.querySelectorAll('#decisionsListContainer .decision-item').length - SHOW;
                                            document.getElementById('decisionsToggleIcon').textContent = expanded ? 'expand_less' : 'expand_more';
                                            document.getElementById('decisionsToggleLabel').textContent = expanded ? 'Weniger anzeigen' : '+ ' + hidden + ' weitere anzeigen';
                                        }
                                        window.toggleDecisions = function() { expanded = !expanded; applyVisibility(); };
                                        applyVisibility();
                                    })();
                                </script>
                                {% endif %}
                            </div>
                        {% endif %}
                        {% if pending_consumable_orders and ('CONSUMABLEMANAGER' in session.get('roles', []) or session.get('is_admin')) %}
                            <div class="mt-6 pt-4 border-t border-slate-100">
                                <h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3">
                                    Offene Bestellbedarfe
                                    <span class="ml-1 px-1.5 py-0.5 rounded bg-slate-100 text-slate-500 font-bold text-xs">{{ pending_consumable_orders | length }}</span>
                                </h3>
                                <div class="space-y-2" id="ordersListContainer">
                                    {% for order in pending_consumable_orders %}
                                        <a href="{{ url_for('serve_consumable_order_page') }}" class="order-item flex items-center gap-3 p-2 rounded-md hover:bg-slate-50 transition-colors group" data-index="{{ loop.index0 }}">
                                            {% if order.isCritical %}
                                                <div class="w-2 h-2 rounded-full bg-red-500 flex-shrink-0"></div>
                                            {% else %}
                                                <div class="w-2 h-2 rounded-full bg-slate-300 flex-shrink-0"></div>
                                            {% endif %}
                                            <span class="text-sm font-medium text-slate-700 group-hover:text-blue-600">{{ order.name }}</span>
                                            <span class="text-xs text-slate-400 ml-auto flex-shrink-0">{{ order.quantity }}</span>
                                            <span class="material-icons-outlined text-slate-300 text-sm opacity-0 group-hover:opacity-100">arrow_forward</span>
                                        </a>
                                    {% endfor %}
                                </div>
                                {% if pending_consumable_orders | length > 3 %}
                                <button id="ordersToggleBtn" onclick="toggleOrders()" class="mt-2 text-xs text-slate-400 hover:text-blue-500 transition-colors flex items-center gap-1">
                                    <span class="material-icons-outlined text-sm" id="ordersToggleIcon">expand_more</span>
                                    <span id="ordersToggleLabel">+ {{ (pending_consumable_orders | length) - 3 }} weitere anzeigen</span>
                                </button>
                                <script>
                                    (function() {
                                        const SHOW = 3;
                                        let expanded = false;
                                        function applyVisibility() {
                                            document.querySelectorAll('#ordersListContainer .order-item').forEach(function(el) {
                                                const idx = parseInt(el.dataset.index, 10);
                                                el.style.display = (expanded || idx < SHOW) ? '' : 'none';
                                            });
                                            const hidden = document.querySelectorAll('#ordersListContainer .order-item').length - SHOW;
                                            document.getElementById('ordersToggleIcon').textContent = expanded ? 'expand_less' : 'expand_more';
                                            document.getElementById('ordersToggleLabel').textContent = expanded ? 'Weniger anzeigen' : '+ ' + hidden + ' weitere anzeigen';
                                        }
                                        window.toggleOrders = function() { expanded = !expanded; applyVisibility(); };
                                        applyVisibility();
                                    })();
                                </script>
                                {% endif %}
                            </div>
                        {% endif %}
                    </div>
                </div>


                <!-- Rechte Spalte: MOTD & Tools (1/3) -->
                <div class="flex flex-col gap-4 h-full">
                    
                    <!-- 1. MOTD -->
                    {% set motd = sfm_texts.get('motd', {'visible': False}) %}
                    
                    {# Definition der Farb-Klassen basierend auf dem Typ #}
                    {% set colors = {
                        'info':    {'bg': 'bg-blue-50', 'border': 'border-blue-500', 'icon': 'text-blue-600', 'title': 'text-blue-800', 'text': 'text-blue-700'},
                        'warning': {'bg': 'bg-amber-50', 'border': 'border-amber-500', 'icon': 'text-amber-600', 'title': 'text-amber-800', 'text': 'text-amber-800'},
                        'danger':  {'bg': 'bg-red-50', 'border': 'border-red-500', 'icon': 'text-red-600', 'title': 'text-red-800', 'text': 'text-red-800'},
                        'success': {'bg': 'bg-green-50', 'border': 'border-green-500', 'icon': 'text-green-600', 'title': 'text-green-800', 'text': 'text-green-800'}
                    } %}
                    {% set c = colors[motd.type] if motd.type in colors else colors['info'] %}

                    {% if motd.visible %}
                        <!-- Sichtbare Box für alle -->
                        <div class="{{ c.bg }} border-l-4 {{ c.border }} p-5 rounded-r-lg relative transition-all duration-300 flex-grow">
                             <div class="flex items-start gap-3">
                                <span class="material-icons-outlined {{ c.icon }} mt-0.5">campaign</span>
                                <div>
                                    <h3 class="font-semibold {{ c.title }}">Mitteilung des Tages</h3>
                                    <p class="text-sm {{ c.text }} mt-1 whitespace-pre-line">{{ motd.text }}</p>
                                </div>
                            </div>
                            {% if 'ADMIN' in session.get('roles', []) %}
                            <button onclick="openMotdModal()" class="absolute top-2 right-2 {{ c.icon }} hover:opacity-70 p-1 rounded-full" title="Mitteilung bearbeiten">
                                <span class="material-icons-outlined" style="font-size: 20px;">settings</span>
                            </button>
                            {% endif %}
                        </div>
                    {% elif 'ADMIN' in session.get('roles', []) %}
                        <!-- Admin-Only Platzhalter wenn ausgeblendet -->
                        <div class="border-2 border-dashed border-slate-300 p-4 rounded-lg relative text-center group hover:border-blue-400 transition-colors cursor-pointer flex-grow flex items-center justify-center" onclick="openMotdModal()">
                             <div class="flex items-center justify-center gap-2 text-slate-400 group-hover:text-blue-500">
                                <span class="material-icons-outlined">visibility_off</span>
                                <span class="text-sm font-medium">Mitteilung des Tages ist ausgeblendet</span>
                            </div>
                        </div>
                    {% endif %}

                    <!-- 2. Global SAP Update (Admin Only) -->
                    {% if session.get('is_admin') %}
                    <div class="bg-indigo-50 border-l-4 border-indigo-500 p-5 rounded-r-lg relative">
                        <div class="flex items-center justify-between">
                            <div class="flex items-center gap-3">
                                <span class="material-icons-outlined text-indigo-600">cloud_sync</span>
                                <div>
                                    <h3 class="font-semibold text-indigo-800">Globales SAP Update</h3>
                                    <p class="text-xs text-indigo-600">Aktualisiert alle Planungs-Boards zentral.</p>
                                </div>
                            </div>
                            <button id="globalSapBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-bold py-2 px-3 rounded shadow-sm flex items-center gap-1">
                                <span class="material-icons-outlined text-sm">upload</span>
                                <span>Update Starten</span>
                            </button>
                            <input type="file" id="globalSapInput" class="hidden" accept=".csv">
                        </div>
                        <div id="globalSapStatus" class="hidden mt-3 pt-3 border-t border-indigo-200 text-xs text-indigo-800"></div>
                    </div>
                    {% endif %}
                </div>
            </section>
            
            <section class="mb-36 grid grid-cols-1 lg:grid-cols-3 gap-6">
                <!-- Mitarbeiter-Kachel -->
                {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) %}
                <div onclick="document.getElementById('employeeStatusModal').classList.remove('hidden')" class="cursor-pointer bg-white rounded-lg border border-slate-200 shadow-sm p-5 transition-shadow duration-200 hover:shadow-md">
                    <div class="flex items-start justify-between">
                        <div>
                            <h3 class="font-bold text-slate-500">Mitarbeiter Heute</h3>
                            <div class="mt-2 flex items-baseline gap-2">
                                <p class="text-3xl font-bold text-slate-800">{{ free_employees_count }}</p>
                                <p class="text-lg text-slate-500">/ {{ present_employees_count }}</p>
                            </div>
                            <p class="text-sm text-slate-500">frei / anwesend</p>
                        </div>
                        <div class="h-10 w-10 bg-neutral-100 flex items-center justify-center rounded-lg">
                            <span class="material-icons-outlined text-sky-900">groups</span>
                        </div>
                    </div>
                </div>
                {% endif %}
                <!-- Testzellen-Kachel -->
                <a href="{{ url_for('serve_test_cell_management_page') }}" class="block bg-white rounded-lg border border-slate-200 shadow-sm p-5 transition-shadow duration-200 hover:shadow-md">
                    <div class="flex items-start justify-between mb-3">
                        <h3 class="font-bold text-slate-500">Testzellen</h3>
                        <div class="h-10 w-10 bg-neutral-100 flex items-center justify-center rounded-lg">
                            <span class="material-icons-outlined text-sky-900">other_houses</span>
                        </div>
                    </div>
                    <div class="flex flex-wrap gap-2">
                        {% for cell in test_cell_statuses %}
                            <span class="inline-flex items-center rounded-full bg-slate-100 px-3 py-1 text-sm font-medium text-slate-700">
                                {{ cell.name }}
                            </span>
                        {% else %}
                            <p class="text-xs text-slate-500">Keine Testzellen konfiguriert.</p>
                        {% endfor %}
                    </div>
                    
                </a>
            </section>
            
            <!-- Planungs-Boards Sektion -->
            <section class="mb-16">
                <div class="flex justify-between items-center mb-4">
                    <h2 class="text-xl font-bold text-slate-700">Planungs-Boards</h2>
                    {% if session.get('is_admin') %}
                    <button id="addTimelineBtn" class="flex-shrink-0 h-12 w-12 flex items-center justify-center rounded-full bg-sky-900 text-white shadow-sm ring-0 ring-inset ring-slate-200 hover:bg-slate-200 transition-colors" title="Neue Timeline erstellen">
                    <span class="material-icons-outlined">add</span>
                    </button>
                    {% endif %}
                </div>
                <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-6 gap-5">
                    {% for timeline in timelines | selectattr('type', 'equalto', 'editable') %}
                    <div class="bg-white rounded-lg border border-slate-200 shadow-sm overflow-hidden flex flex-col transition-shadow duration-200 hover:shadow-lg">
                        <div class="h-32 bg-gradient-to-br from-blue-100 to-indigo-200 flex items-center justify-center">
                            <span class="material-icons-outlined text-blue-500" style="font-size: 48px;">edit_calendar</span>
                        </div>
                        <div class="p-5 flex flex-col flex-grow">
                            <div class="flex justify-between items-start mb-1">
                                <h3 class="text-lg font-bold text-slate-800">{{ timeline.name }}</h3>
                                {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) %}
                                <button class="edit-timeline-btn text-slate-400 hover:text-blue-600 -mr-2 -mt-2 p-2 rounded-full" data-timeline-id="{{ timeline.id }}" title="Einstellungen bearbeiten">
                                    <span class="material-icons-outlined" style="font-size: 20px;">settings</span>
                                </button>
                                {% endif %}
                            </div>
                            <p class="text-xs text-slate-400 mb-3">Typ: {{ timeline.type }}</p>
                            <div class="flex-grow"></div>
                            <div class="mt-4 pt-4 border-t border-slate-100 flex items-center gap-3">
                                <a href="{{ url_for('serve_timeline_page', timeline_id=timeline.id) }}" class="flex-1 inline-flex items-center justify-center gap-x-2 rounded-md bg-white px-3 py-2 text-sm font-semibold text-slate-700 shadow-sm ring-1 ring-inset ring-slate-200 hover:bg-slate-100">
                                    <span class="material-icons-outlined" style="font-size: 18px;">visibility</span>
                                    View
                                </a>
                                {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) %}
                                <a href="{{ url_for('serve_editable_timeline_page', timeline_id=timeline.id) }}" class="flex-1 inline-flex items-center justify-center gap-x-2 rounded-md bg-sky-900 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
                                    <span class="material-icons-outlined" style="font-size: 18px;">edit_calendar</span>
                                    Plan
                                </a>
                                {% endif %}
                            </div>
                        </div>
                    </div>
                    {% endfor %}
                </div>
            </section>

            <!-- Übersichten Sektion -->
            {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) %}
            <section class="mb-16">
                <h2 class="text-xl font-bold text-slate-700 mb-36"></h2>
                <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-6 gap-5">
                     {% for timeline in timelines | selectattr('type', 'equalto', 'static') %}
                     <div class="bg-white rounded-lg border border-slate-200 shadow-sm overflow-hidden flex flex-col transition-shadow duration-200 hover:shadow-lg">
                        <div class="h-32 bg-gradient-to-br from-slate-100 to-slate-200 flex items-center justify-center">
                            <span class="material-icons-outlined text-slate-500" style="font-size: 48px;">visibility</span>
                        </div>
                        <div class="p-5 flex flex-col flex-grow">
                            <div class="flex justify-between items-start mb-1">
                                <h3 class="text-lg font-bold text-slate-800">{{ timeline.name }}</h3>
                                {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) %}
                                <button class="edit-timeline-btn text-slate-400 hover:text-blue-600 -mr-2 -mt-2 p-2 rounded-full" data-timeline-id="{{ timeline.id }}" title="Einstellungen bearbeiten">
                                    <span class="material-icons-outlined" style="font-size: 20px;">settings</span>
                                </button>
                                {% endif %}
                            </div>
                            <p class="text-xs text-slate-400 mb-3">Typ: {{ timeline.type }}</p>
                            <div class="flex-grow"></div>
                            <div class="mt-4 pt-4 border-t border-slate-100 flex items-center">
                                <a href="{{ url_for('serve_timeline_page', timeline_id=timeline.id) }}" class="w-full inline-flex items-center justify-center gap-x-2 rounded-md bg-white px-3 py-2 text-sm font-semibold text-slate-700 shadow-sm ring-1 ring-inset ring-slate-200 hover:bg-slate-100">
                                    <span class="material-icons-outlined" style="font-size: 18px;">visibility</span>
                                    Öffnen
                                </a>
                            </div>
                        </div>
                    </div>
                    {% endfor %}
                </div>
            </section>
            {% endif %}

            <!-- Weitere Module (Grid angepasst auf 4 Spalten für Large Screens) -->
            <section class="mb-32">
                <h2 class="text-xl font-bold text-slate-700 mb-4">Weitere Module</h2>
                <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">

                    <!-- SWAT -->
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTMECHANIC' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) or 'TESTENGINEER' in session.get('roles', []) or 'MAINTENANCEMANAGER' in session.get('roles', []) or 'HARDWAREMANAGER' in session.get('roles', []) %}
                    <a href="{{ url_for('serve_maintenance_page') }}" class="block bg-white rounded-lg border border-slate-200 shadow-sm p-5 transition-all duration-200 hover:shadow-lg hover:-translate-y-1">
                        <div class="flex items-center gap-4">
                            <div class="h-12 w-12 bg-gradient-to-br from-amber-100 to-orange-200 flex items-center justify-center rounded-lg">
                                <span class="material-icons-outlined text-amber-600">engineering</span>
                            </div>
                            <div>
                                <h3 class="text-lg font-bold text-slate-800">Maintenance</h3>
                                <p class="text-sm text-slate-500">Maintenance Center</p>
                            </div>
                        </div>
                    </a>
                    {% endif %}

                    <!-- Hardware -->
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTMECHANIC' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) or 'TESTENGINEER' in session.get('roles', []) or 'MAINTENANCEMANAGER' in session.get('roles', []) or 'HARDWAREMANAGER' in session.get('roles', []) %}
                    <a href="{{ url_for('serve_test_equipment_page') }}" class="block bg-white rounded-lg border border-slate-200 shadow-sm p-5 transition-all duration-200 hover:shadow-lg hover:-translate-y-1">
                        <div class="flex items-center gap-4">
                            <div class="h-12 w-12 bg-gradient-to-br from-teal-100 to-cyan-200 flex items-center justify-center rounded-lg">
                                <span class="material-icons-outlined text-teal-600">memory</span>
                            </div>
                            <div>
                                <h3 class="text-lg font-bold text-slate-800">Hardware</h3>
                                <p class="text-sm text-slate-500">Übersicht & Verwaltung</p>
                            </div>
                        </div>
                    </a>
                    {% endif %}

                    <!-- NEU: Anlagenübersicht (Platzhalter) -->
                    <a href="{{ url_for('serve_facility_viewer') }}" class="block bg-white rounded-lg border border-slate-200 shadow-sm p-5 transition-all duration-200 hover:shadow-lg hover:-translate-y-1">
                        <div class="flex items-center gap-4">
                            <div class="h-12 w-12 bg-gradient-to-br from-indigo-100 to-blue-200 flex items-center justify-center rounded-lg">
                                <span class="material-icons-outlined text-indigo-600">warehouse</span>
                            </div>
                            <div>
                                <h3 class="text-lg font-bold text-slate-800">Anlagenübersicht</h3>
                                <p class="text-sm text-slate-500">Live Status & Monitoring</p>
                            </div>
                        </div>
                    </a>
                    <!-- Bestellsystem -->
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTMECHANIC' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) or 'TESTENGINEER' in session.get('roles', []) or 'MAINTENANCEMANAGER' in session.get('roles', []) or 'HARDWAREMANAGER' in session.get('roles', []) %}
                    <a href="{{ url_for('serve_consumable_order_page') }}" class="block bg-white rounded-lg border border-slate-200 shadow-sm p-5 transition-all duration-200 hover:shadow-lg hover:-translate-y-1">
                        <div class="flex items-center gap-4">
                            <div class="h-12 w-12 bg-gradient-to-br from-violet-100 to-purple-200 flex items-center justify-center rounded-lg">
                                <span class="material-icons-outlined text-violet-600">shopping_cart</span>
                            </div>
                            <div>
                                <h3 class="text-lg font-bold text-slate-800">Bestellsystem</h3>
                                <p class="text-sm text-slate-500">Verbrauchsmaterialien ordern</p>
                            </div>
                        </div>
                    </a>
                    {% endif %}
                </div>
            </section>
<!-- Trainings Vorschau -->
            <section class="mb-24">
                <div class="flex justify-between items-center mb-4">
                    <h2 class="text-xl font-bold text-slate-700">Bevorstehende Trainings & Events</h2>
                    <a href="{{ url_for('serve_training_page') }}" class="text-sm font-medium text-blue-600 hover:text-blue-800">Alle anzeigen</a>
                </div>
                
                <div class="flex space-x-6 overflow-x-auto pb-4 -mx-8 px-8 snap-x">
                    {% if upcoming_trainings %}
                        {% for t in upcoming_trainings %}
                        
                        {# Farb-Rotation als Fallback #}
                        {% set bg_class = loop.cycle('bg-slate-200', 'bg-slate-400', 'bg-slate-300') %}
                        
                        <!-- Event-Karte -->
                        <div class="event-card flex-shrink-0 bg-white rounded-lg border border-slate-200 shadow-sm overflow-hidden w-72 snap-center hover:shadow-md transition-shadow">
                            <!-- Header (Bild oder Farbe) -->
                            <!-- 'bg-cover bg-center' sorgt für gute Skalierung des Bildes -->
                            <div class="h-32 {{ bg_class }} flex items-end p-4 relative bg-cover bg-center group" 
                                 style="{% if t.image %}background-image: url('{{ t.image }}');{% endif %}">
                               
                               <!-- Dunkles Overlay für Lesbarkeit, nur wenn Bild da ist -->
                               {% if t.image %}
                               <div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
                               {% else %}
                               <!-- Deko-Icon wenn kein Bild -->
                               <span class="material-icons-outlined absolute right-2 top-2 text-white opacity-20 text-6xl transform group-hover:scale-110 transition-transform">event</span>
                               {% endif %}
                               
                               <!-- Datum Box -->
                               <div class="relative z-10 text-white font-bold drop-shadow-md">
                                    <span class="block text-sm opacity-90 uppercase tracking-wider">{{ t.date_month }}</span>
                                    <span class="text-3xl leading-none">{{ t.date_day }}</span>
                               </div>
                            </div>
                            
                            <div class="p-4">
                                <p class="text-xs font-bold text-blue-600 mb-1 uppercase tracking-wide">{{ t.category }}</p>
                                <h4 class="font-bold text-slate-800 text-sm mb-2 line-clamp-2 min-h-[2.5rem]">{{ t.title }}</h4>
                                
                                <div class="flex items-center gap-3 text-xs text-slate-500">
                                    <span class="flex items-center gap-1"><span class="material-icons-outlined text-[14px]">schedule</span> {{ t.time }}</span>
                                    {% if t.location %}
                                    <span class="flex items-center gap-1 truncate max-w-[80px]" title="{{ t.location }}"><span class="material-icons-outlined text-[14px]">place</span> {{ t.location }}</span>
                                    {% endif %}
                                </div>
                                
                                <div class="mt-4 pt-3 border-t border-slate-100 flex justify-end">
                                    <a href="{{ url_for('serve_training_page') }}" class="text-slate-400 hover:text-blue-600 transition-colors p-1">
                                        <span class="material-icons-outlined text-xl">arrow_forward</span>
                                    </a>
                                </div>
                            </div>
                        </div>
                        {% endfor %}
                        
                        <!-- "Mehr" Karte -->
                        <a href="{{ url_for('serve_training_page') }}" class="event-card flex-shrink-0 bg-slate-50 rounded-lg border border-dashed border-slate-300 w-40 flex flex-col items-center justify-center text-slate-400 hover:text-blue-600 hover:border-blue-400 hover:bg-blue-50 transition-all snap-center cursor-pointer">
                            <span class="material-icons-outlined text-3xl mb-2">calendar_month</span>
                            <span class="text-sm font-medium">Zum Kalender</span>
                        </a>

                    {% else %}
                        <!-- Empty State -->
                        <div class="w-full text-center py-10 bg-slate-50 rounded-lg border border-dashed border-slate-300 text-slate-400 italic">
                            Aktuell keine Trainings geplant. <a href="{{ url_for('serve_training_page') }}" class="text-blue-600 hover:underline not-italic ml-1">Jetzt planen</a>
                        </div>
                    {% endif %}
                </div>
            </section>
        </div>
    </main>
</div>

<!-- Modals für Timeline Erstellung (bleibt unverändert) -->
<div id="addTimelineModal" class="fixed inset-0 bg-gray-600 bg-opacity-75 flex items-center justify-center hidden z-50 p-4">
    <div class="relative mx-auto border w-full max-w-2xl shadow-lg rounded-md bg-white flex flex-col max-h-[90vh]">
        <div class="flex justify-between items-center p-5 border-b flex-shrink-0">
            <h3 class="text-xl leading-6 font-medium text-gray-900">Neue Timeline erstellen</h3>
            <button id="closeTimelineModalBtn" class="text-gray-400 hover:text-gray-600">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <form id="addTimelineForm" class="mt-4 px-2 sm:px-7 py-3 space-y-4 overflow-y-auto flex-1">
            <div>
                <label for="timelineName" class="block text-sm font-medium text-gray-700">Name der Timeline *</label>
                <input type="text" id="timelineName" name="timelineName" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md text-sm shadow-sm" required placeholder="z.B. Planung SWAT Team">
            </div>
             <div>
                <label for="timelineId" class="block text-sm font-medium text-gray-700">Eindeutige ID *</label>
                <input type="text" id="timelineId" name="timelineId" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md text-sm shadow-sm" required placeholder="z.B. swat_team">
            </div>
            <div>
                <label class="block text-sm font-medium text-gray-700">Typ *</label>
                <select id="timelineType" name="timelineType" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
                    <option value="editable">Editable (Planung & Ansicht)</option>
                    <option value="static">Static (Nur Ansicht)</option>
                </select>
            </div>
            <div id="editableOptions" class="space-y-4 pt-4 border-t">
                 <div>
                    <label class="block text-sm font-medium text-gray-700">Sichtbare Testzellen</label>
                    <div id="modalTestCells" class="mt-2 grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-32 overflow-y-auto border p-2 rounded-md"></div>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700">Zuweisungsgruppen</label>
                    <div id="modalAssignmentGroups" class="mt-2 grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-32 overflow-y-auto border p-2 rounded-md"></div>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700">Hardware-Kits</label>
                    <p class="text-xs text-gray-500 mt-0.5">Kits, die für Projekte in dieser Timeline reserviert werden können.</p>
                    <div id="modalHardwareKits" class="mt-2 grid grid-cols-1 gap-1 max-h-40 overflow-y-auto border p-2 rounded-md"><p class="text-xs text-gray-400">Wird geladen...</p></div>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700">SAP-Filter</label>
                    <div id="sapFilterContainer" class="mt-2 space-y-3 p-3 border rounded-md bg-slate-50"></div>
                    <button id="addSapFilterRuleBtn" type="button" class="mt-2 text-sm font-medium text-blue-600 hover:text-blue-800">+ Filterregel hinzufügen</button>
                </div>
            </div>
            <div id="staticOptions" class="space-y-4 pt-4 border-t hidden">
                 <div>
                    <label class="block text-sm font-medium text-gray-700">Datenquellen</label>
                    <div id="modalDataSources" class="mt-2 grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-40 overflow-y-auto border p-2 rounded-md"></div>
                </div>
            </div>
        </form>
        <div class="items-center px-4 py-3 bg-gray-50 rounded-b-md border-t flex-shrink-0">
            <div id="modalError" class="text-red-600 text-sm mb-2 text-center hidden"></div>
            <div class="flex justify-between items-center">
                <button id="deleteTimelineBtn" type="button" class="hidden items-center gap-x-1.5 rounded-md bg-red-50 px-3 py-2 text-sm font-semibold text-red-600 shadow-sm ring-1 ring-inset ring-red-200 hover:bg-red-100">
                    <span class="material-icons-outlined text-base -ml-1">delete_outline</span> Löschen
                </button>
                <div class="flex justify-end space-x-3 flex-grow">
                    <button id="cancelTimelineModalBtn" type="button" class="px-4 py-2 bg-gray-200 text-gray-700 text-base font-medium rounded-md shadow-sm hover:bg-gray-300">Abbrechen</button>
                    <button id="saveTimelineBtn" type="submit" form="addTimelineForm" class="px-4 py-2 bg-blue-600 text-white text-base font-medium rounded-md shadow-sm hover:bg-blue-700">Timeline erstellen</button>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- MOTD Bearbeiten Modal -->
<div id="motdModal" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full flex items-center justify-center hidden z-50 p-4">
    <div class="relative mx-auto p-5 border w-full max-w-md shadow-lg rounded-md bg-white">
        <div class="flex justify-between items-center pb-3 border-b">
            <h3 class="text-lg font-medium text-gray-900">Mitteilung bearbeiten</h3>
            <button onclick="document.getElementById('motdModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <div class="mt-4 space-y-4">
            <div>
                <label class="block text-sm font-medium text-gray-700">Typ / Dringlichkeit</label>
                <div class="mt-2 grid grid-cols-4 gap-2">
                    <button type="button" onclick="selectMotdType('info')" class="motd-type-btn p-2 rounded border border-blue-200 bg-blue-50 text-blue-700 hover:ring-2 hover:ring-blue-400 text-center text-xs font-bold" data-type="info">Info</button>
                    <button type="button" onclick="selectMotdType('warning')" class="motd-type-btn p-2 rounded border-amber-200 bg-amber-50 text-amber-700 hover:ring-2 hover:ring-amber-400 text-center text-xs font-bold" data-type="warning">Warnung</button>
                    <button type="button" onclick="selectMotdType('danger')" class="motd-type-btn p-2 rounded border-red-200 bg-red-50 text-red-700 hover:ring-2 hover:ring-red-400 text-center text-xs font-bold" data-type="danger">Alarm</button>
                    <button type="button" onclick="selectMotdType('success')" class="motd-type-btn p-2 rounded border-green-200 bg-green-50 text-green-700 hover:ring-2 hover:ring-green-400 text-center text-xs font-bold" data-type="success">Erfolg</button>
                </div>
                <input type="hidden" id="motdTypeInput">
            </div>
            <div>
                <label class="block text-sm font-medium text-gray-700">Nachricht</label>
                <textarea id="motdTextInput" rows="3" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"></textarea>
            </div>
            <div class="flex items-center">
                <input id="motdVisibleInput" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
                <label for="motdVisibleInput" class="ml-2 block text-sm text-gray-900">Mitteilung auf dem Dashboard anzeigen</label>
            </div>
        </div>
        <div class="mt-5 flex justify-end gap-2">
            <button onclick="document.getElementById('motdModal').classList.add('hidden')" class="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-300">Abbrechen</button>
            <button onclick="saveMotd()" class="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700">Speichern</button>
        </div>
    </div>
</div>
<!-- NEUES MODAL: Mitarbeiter-Status Heute -->
<div id="employeeStatusModal" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full flex items-center justify-center hidden z-50 p-4">
    <div class="relative mx-auto p-6 border w-full max-w-md shadow-lg rounded-md bg-white">
        <div class="flex justify-between items-center pb-3 border-b">
            <h3 class="text-xl leading-6 font-medium text-gray-900">Mitarbeiter-Status Heute</h3>
            <button onclick="document.getElementById('employeeStatusModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <div class="mt-4 max-h-[70vh] overflow-y-auto pr-2 space-y-6">
            <!-- Verfügbare Mitarbeiter -->
            <div>
                <h4 class="text-sm font-bold text-green-700 uppercase tracking-wider mb-2">Verfügbar ({{ free_employees_count }})</h4>
                <ul class="space-y-1 text-sm text-slate-600">
                    {% for emp in free_employees %}
                    <li>{{ emp.name }}</li>
                    {% else %}
                    <li class="italic text-slate-400">Keine freien Mitarbeiter.</li>
                    {% endfor %}
                </ul>
            </div>
            <!-- Verplante Mitarbeiter -->
            <div>
                <h4 class="text-sm font-bold text-slate-500 uppercase tracking-wider mb-2">Verplant ({{ busy_employees|length }})</h4>
                <ul class="space-y-2 text-sm text-slate-600">
                    {% for emp in busy_employees %}
                    <li>
                        {{ emp.name }} 
                        <span class="text-xs ml-2 bg-slate-100 text-slate-500 px-2 py-0.5 rounded-full font-mono">{{ emp.assignment }}</span>
                    </li>
                    {% else %}
                    <li class="italic text-slate-400">Keine verplanten Mitarbeiter.</li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    </div>
</div>
<script>
    const currentMotd = {{ sfm_texts.get('motd', {}) | tojson }};

    function openMotdModal() {
        document.getElementById('motdTextInput').value = currentMotd.text || '';
        document.getElementById('motdVisibleInput').checked = currentMotd.visible;
        selectMotdType(currentMotd.type || 'info');
        document.getElementById('motdModal').classList.remove('hidden');
    }

    function selectMotdType(type) {
        document.getElementById('motdTypeInput').value = type;
        document.querySelectorAll('.motd-type-btn').forEach(btn => {
            if (btn.dataset.type === type) {
                btn.classList.add('ring-2', 'ring-offset-1', 'ring-slate-400');
            } else {
                btn.classList.remove('ring-2', 'ring-offset-1', 'ring-slate-400');
            }
        });
    }

    async function saveMotd() {
        const payload = {
            key: 'motd',
            text: document.getElementById('motdTextInput').value,
            type: document.getElementById('motdTypeInput').value,
            visible: document.getElementById('motdVisibleInput').checked
        };

        try {
            const response = await fetch("{{ url_for('api_save_sfm_text') }}", {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            });

            if (response.ok) {
                location.reload(); // Einfachster Weg, um das Dashboard zu aktualisieren
            } else {
                alert("Fehler beim Speichern.");
            }
        } catch (error) {
            console.error(error);
            alert("Netzwerkfehler.");
        }
    }
document.addEventListener('DOMContentLoaded', () => {
    // Sidebar Logik
    const sidebar = document.getElementById('sidebar');
    const sidebarToggleBtn = document.getElementById('sidebar-toggle');
    if (sidebar && sidebarToggleBtn) {
        const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
        const applyState = (isCollapsed) => {
            if (isCollapsed) { sidebar.classList.add('is-collapsed'); toggleBtnIcon.textContent = 'menu'; } 
            else { sidebar.classList.remove('is-collapsed'); toggleBtnIcon.textContent = 'chevron_left'; }
        };
        sidebarToggleBtn.addEventListener('click', () => {
            const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
            localStorage.setItem('sidebarCollapsed', isNowCollapsed);
            applyState(isNowCollapsed);
        });
        applyState(localStorage.getItem('sidebarCollapsed') === 'true');
    }

    // --- Global SAP Update Logic ---
    const globalSapBtn = document.getElementById('globalSapBtn');
    const globalSapInput = document.getElementById('globalSapInput');
    const globalSapStatus = document.getElementById('globalSapStatus');

    if (globalSapBtn && globalSapInput) {
        globalSapBtn.addEventListener('click', () => globalSapInput.click());
        globalSapInput.addEventListener('change', async (e) => {
            if (e.target.files.length === 0) return;
            const file = e.target.files[0];

            globalSapBtn.disabled = true;
            globalSapBtn.innerHTML = '<span class="material-icons-outlined text-sm animate-spin">sync</span> <span>Verarbeite...</span>';
            globalSapStatus.classList.remove('hidden');
            globalSapStatus.innerHTML = 'Daten werden analysiert und verteilt... Bitte warten.';
            globalSapStatus.className = 'mt-3 pt-3 border-t border-indigo-200 text-xs text-indigo-800 block';

            const formData = new FormData();
            formData.append('sap_file', file);

            try {
                const response = await fetch("{{ url_for('api_global_sap_update') }}", {
                    method: 'POST',
                    body: formData
                });
                const result = await response.json();

                if (response.ok) {
                    globalSapStatus.innerHTML = `<strong>Erfolg:</strong><br>${result.details}`;
                    globalSapStatus.className = 'mt-3 pt-3 border-t border-indigo-200 text-xs text-green-700 block';
                } else {
                    throw new Error(result.error || 'Unbekannter Fehler');
                }
            } catch (error) {
                globalSapStatus.textContent = `Fehler: ${error.message}`;
                globalSapStatus.className = 'mt-3 pt-3 border-t border-indigo-200 text-xs text-red-600 block';
            } finally {
                globalSapBtn.disabled = false;
                globalSapBtn.innerHTML = '<span class="material-icons-outlined text-sm">upload</span> <span>Update Starten</span>';
                globalSapInput.value = ''; 
            }
        });
    }

    // --- Bestehende Timeline Modal Logik ---
    const addTimelineBtn = document.getElementById('addTimelineBtn');
    if (!addTimelineBtn) return;

    const modal = document.getElementById('addTimelineModal');
    const closeBtn = document.getElementById('closeTimelineModalBtn');
    const cancelBtn = document.getElementById('cancelTimelineModalBtn');
    const form = document.getElementById('addTimelineForm');
    const modalTitle = modal.querySelector('h3');
    const saveBtn = document.getElementById('saveTimelineBtn');
    const deleteBtn = document.getElementById('deleteTimelineBtn');
    const timelineNameInput = document.getElementById('timelineName');
    const timelineIdInput = document.getElementById('timelineId');
    const timelineTypeSelect = document.getElementById('timelineType');
    const editableOptionsDiv = document.getElementById('editableOptions');
    const staticOptionsDiv = document.getElementById('staticOptions');
    const modalError = document.getElementById('modalError');
    const sapFilterContainer = document.getElementById('sapFilterContainer');
    const addSapFilterRuleBtn = document.getElementById('addSapFilterRuleBtn');

    let isEditMode = false;
    let editingTimelineId = null;

    function openModal() { form.reset(); modalError.classList.add('hidden'); modal.classList.remove('hidden'); }
    function closeModal() { modal.classList.add('hidden'); isEditMode = false; editingTimelineId = null; }

    function addSapFilterRule(column = '', values = '') {
        const ruleDiv = document.createElement('div');
        ruleDiv.className = 'flex items-center gap-2 sap-filter-rule';
        ruleDiv.innerHTML = `<input type="text" name="sap_filter_column" class="flex-1 block w-full px-2 py-1.5 bg-white border border-gray-300 rounded-md text-sm" placeholder="Spaltenname" value="${column}"><input type="text" name="sap_filter_values" class="flex-1 block w-full px-2 py-1.5 bg-white border border-gray-300 rounded-md text-sm" placeholder="Werte (kommagetrennt)" value="${values}"><button type="button" class="remove-rule-btn text-red-500 hover:text-red-700 p-1"><span class="material-icons-outlined text-xl">delete_outline</span></button>`;
        ruleDiv.querySelector('.remove-rule-btn').addEventListener('click', () => { ruleDiv.remove(); if (sapFilterContainer.querySelectorAll('.sap-filter-rule').length === 0) sapFilterContainer.innerHTML = '<p class="text-xs text-gray-500 text-center">Keine Filterregeln definiert.</p>'; });
        if (!sapFilterContainer.querySelector('.sap-filter-rule')) sapFilterContainer.innerHTML = '';
        sapFilterContainer.appendChild(ruleDiv);
    }

    async function populateModalWithOptions() {
        try {
            const [cellsRes, presenceRes, configRes, kitsRes] = await Promise.all([fetch("{{ url_for('api_get_test_cells') }}"), fetch("{{ url_for('api_get_employee_presence_data') }}"), fetch("{{ url_for('get_app_config') }}"), fetch("{{ url_for('api_get_hardware_kits_list') }}")]);
            if (!cellsRes.ok || !presenceRes.ok || !configRes.ok) throw new Error('Datenfehler');
            const allCells = await cellsRes.json(); const presenceData = await presenceRes.json(); const configData = await configRes.json();
            const allKits = kitsRes.ok ? await kitsRes.json() : [];
            document.getElementById('modalTestCells').innerHTML = allCells.map(cell => `<label class="flex items-center space-x-2"><input type="checkbox" name="visible_test_cells" value="${cell.ID}" class="h-4 w-4 rounded border-gray-300"><span class="text-sm text-gray-700">${cell.TESTCELL} (${cell.ID})</span></label>`).join('') || '<p class="text-xs text-gray-500">Keine Testzellen.</p>';
            const allGroups = [...new Set(presenceData.employee_groups.map(g => g.group_name))].sort();
            document.getElementById('modalAssignmentGroups').innerHTML = allGroups.map(group => `<label class="flex items-center space-x-2"><input type="checkbox" name="assignment_groups" value="${group}" class="h-4 w-4 rounded border-gray-300"><span class="text-sm text-gray-700">${group}</span></label>`).join('');
            document.getElementById('modalHardwareKits').innerHTML = allKits.length > 0 ? allKits.map(kit => { const codeBadge = kit.code ? `<span style="font-size:0.65rem;background:#cffafe;color:#0e7490;border-radius:3px;padding:0 4px;margin-right:4px;font-family:monospace;font-weight:700;">${kit.code}</span>` : ''; const engines = kit.engine_types.length > 0 ? `<span class="text-xs text-gray-400 ml-1">(${kit.engine_types.join(', ')})</span>` : ''; return `<label class="flex items-center gap-2 py-0.5"><input type="checkbox" name="available_hardware_kits" value="${kit.id}" class="h-4 w-4 rounded border-gray-300 flex-shrink-0"><span class="text-sm text-gray-700 flex items-center flex-wrap">${codeBadge}${kit.name}${engines}</span></label>`; }).join('') : '<p class="text-xs text-gray-500">Keine Hardware-Kits vorhanden.</p>';
            const sourceableTimelines = (configData.timelines || []).filter(tl => tl.data_file);
            document.getElementById('modalDataSources').innerHTML = sourceableTimelines.map(tl => `<label class="flex items-center space-x-2"><input type="checkbox" name="data_files_source" value="${tl.data_file}" class="h-4 w-4 rounded border-gray-300"><span class="text-sm text-gray-700">${tl.name}</span></label>`).join('') || '<p class="text-xs text-gray-500">Keine Quellen.</p>';
        } catch(error) { modalError.textContent = 'Fehler: ' + error.message; modalError.classList.remove('hidden'); }
    }

    addTimelineBtn.addEventListener('click', async () => { isEditMode = false; editingTimelineId = null; openModal(); modalTitle.textContent = 'Neue Timeline erstellen'; saveBtn.textContent = 'Erstellen'; timelineIdInput.readOnly = false; timelineIdInput.classList.remove('bg-gray-100'); deleteBtn.classList.add('hidden'); sapFilterContainer.innerHTML = '<p class="text-xs text-gray-500 text-center">Keine Filterregeln definiert.</p>'; editableOptionsDiv.style.display = 'block'; staticOptionsDiv.style.display = 'none'; await populateModalWithOptions(); });

    document.querySelector('main').addEventListener('click', async (e) => {
        const editButton = e.target.closest('.edit-timeline-btn');
        if (!editButton) return;
        isEditMode = true; editingTimelineId = editButton.dataset.timelineId; openModal(); modalTitle.textContent = 'Timeline bearbeiten'; saveBtn.textContent = 'Speichern'; timelineIdInput.readOnly = true; timelineIdInput.classList.add('bg-gray-100'); deleteBtn.classList.remove('hidden'); await populateModalWithOptions();
        try {
            const response = await fetch(`/api/timeline_config/${editingTimelineId}`); if (!response.ok) throw new Error('Fehler beim Laden');
            const data = await response.json();
            timelineNameInput.value = data.name; timelineIdInput.value = data.id; timelineTypeSelect.value = data.type;
            if (data.type === 'editable') {
                editableOptionsDiv.style.display = 'block'; staticOptionsDiv.style.display = 'none';
                (data.visible_test_cells || []).forEach(id => { const cb = form.querySelector(`input[name="visible_test_cells"][value="${id}"]`); if (cb) cb.checked = true; });
                (data.assignment_groups || []).forEach(group => { const cb = form.querySelector(`input[name="assignment_groups"][value="${group}"]`); if (cb) cb.checked = true; });
                (data.available_hardware_kits || []).forEach(kid => { const cb = form.querySelector(`input[name="available_hardware_kits"][value="${kid}"]`); if (cb) cb.checked = true; });
                sapFilterContainer.innerHTML = ''; const rules = Object.entries(data.sap_filter || {});
                if (rules.length > 0) rules.forEach(([c, v]) => addSapFilterRule(c, v.join(', '))); else sapFilterContainer.innerHTML = '<p class="text-xs text-gray-500 text-center">Keine Filterregeln.</p>';
            } else {
                editableOptionsDiv.style.display = 'none'; staticOptionsDiv.style.display = 'block';
                (data.data_files || []).forEach(f => { const cb = form.querySelector(`input[name="data_files_source"][value="${f}"]`); if (cb) cb.checked = true; });
            }
        } catch (error) { modalError.textContent = error.message; modalError.classList.remove('hidden'); }
    });

    closeBtn.addEventListener('click', closeModal); cancelBtn.addEventListener('click', closeModal);
    addSapFilterRuleBtn.addEventListener('click', () => addSapFilterRule());
    timelineNameInput.addEventListener('input', () => { if (!isEditMode) timelineIdInput.value = timelineNameInput.value.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, ''); });
    timelineTypeSelect.addEventListener('change', () => { const isEditable = timelineTypeSelect.value === 'editable'; editableOptionsDiv.style.display = isEditable ? 'block' : 'none'; staticOptionsDiv.style.display = isEditable ? 'none' : 'block'; });

    deleteBtn.addEventListener('click', async () => {
        if (!isEditMode || !confirm(`Timeline wirklich löschen?`)) return;
        try { await fetch(`/api/admin/delete_timeline/${editingTimelineId}`, { method: 'DELETE' }); window.location.reload(); } catch (error) { modalError.textContent = error.message; modalError.classList.remove('hidden'); }
    });

    form.addEventListener('submit', async (e) => {
        e.preventDefault(); const formData = new FormData(form); const type = formData.get('timelineType');
        let data = { name: formData.get('timelineName'), id: formData.get('timelineId'), type: type };
        if (type === 'editable') {
            const sapFilter = {};
            sapFilterContainer.querySelectorAll('.sap-filter-rule').forEach(ruleDiv => {
                const c = ruleDiv.querySelector('input[name="sap_filter_column"]').value.trim();
                const v = ruleDiv.querySelector('input[name="sap_filter_values"]').value.trim();
                if (c && v) sapFilter[c] = v.split(',').map(s => s.trim()).filter(Boolean);
            });
            data.visible_test_cells = formData.getAll('visible_test_cells'); data.assignment_groups = formData.getAll('assignment_groups'); data.available_hardware_kits = formData.getAll('available_hardware_kits'); data.sap_filter = sapFilter;
        } else { data.data_files = formData.getAll('data_files_source'); }

        try {
            await fetch(isEditMode ? `/api/admin/update_timeline/${editingTimelineId}` : "{{ url_for('api_create_timeline') }}", { method: isEditMode ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
            window.location.reload();
        } catch (error) { modalError.textContent = error.message; modalError.classList.remove('hidden'); }
    });
});
</script>
</body>
</html>
"""

PROCESS_TEMPLATES_MANAGEMENT_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Prozess-Templates verwalten</title>
    <link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?display=swap&family=Inter:wght@400;500;600;700">
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .type-list-item.selected { background-color: #eef2ff; color: #4338ca; font-weight: 600; }

        /* Styles for TFS Step List */
        .step-list { list-style: none; padding: 0; margin: 0; space-y: 0.75rem; }
        .step-list-item-wrapper.main-step > .step-list-item { background-color: #f9fafb; border-top-width: 2px; border-bottom-width: 2px; border-color: #e5e7eb; }
        .step-list-item-wrapper.main-step .step-name-input { font-weight: 600; font-size: 0.95rem; }
        .step-list-item-wrapper.sub-step { margin-left: 2.5rem; }
        .step-list-item { background-color: white; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1rem; display: grid; grid-template-columns: auto 1fr 1fr auto auto; gap: 1rem; align-items: center; }
        .step-list-item .drag-handle { cursor: grab; color: #9ca3af; }

        /* Styles for Timeline Event List (NEU) */
        .timeline-event-item .drag-handle { cursor: grab; color: #9ca3af; }

        .sortable-ghost { background-color: #dbeafe; opacity: 0.7; }
        .form-input, .form-select { @apply block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm; }
        .action-button { @apply text-slate-400 hover:text-slate-700 p-2 rounded-full hover:bg-slate-100; }

        /* Styles for Tabs (NEU) */
        .tab-btn.text-blue-600 { color: #2563eb; }
        .tab-btn.border-blue-600 { border-color: #2563eb; }

        /* Instruction Items (3. Ebene im Template-Editor) */
        .instructions-section { border-top: 1px solid #f1f5f9; padding-top: 0.5rem; margin-top: 0.5rem; }
        .instruction-item { display: flex; align-items: flex-start; gap: 0.375rem; padding: 0.375rem 0.5rem; background: white; border: 1px solid #e2e8f0; border-radius: 0.375rem; }
        .instruction-item:hover { border-color: #cbd5e1; }
        .inst-toggle-icon { transition: transform 0.2s ease; display: inline-block; }

        /* Styles für die einklappbare Sidebar */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }

        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator {
            display: none;
        }
        #sidebar.is-collapsed .sidebar-separator {
            display: block;
            height: 1px;
            background-color: #e2e8f0;
            margin: 0.5rem 0.75rem;
        }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Engine Types</h1>
        </header>
        <div class="p-4 sm:p-6 lg:p-8">
            <div class="max-w-7xl mx-auto">
                <div class="flex justify-between items-center mb-6">
                    <div>
                        <h2 class="text-2xl font-bold tracking-tight text-slate-900">TFS & Timeline</h2>
                        <p class="text-sm text-slate-500 mt-1">Definieren Sie hier die Prozessabfolgen für jeden Triebwerkstyp.</p>
                    </div>
                    <button id="saveAllChangesBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 disabled:opacity-50">
                        <span class="material-icons-outlined text-base -ml-0.5">save</span>
                        Änderungen speichern
                    </button>
                </div>

            <div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
                <!-- Linke Spalte -->
                <div class="lg:col-span-1 bg-white p-4 rounded-lg shadow-md border border-slate-200">
                    <h2 class="text-lg font-semibold text-slate-800 mb-3">Engine Types</h2>
                    <ul id="engineTypesList" class="space-y-1"></ul>
                    <div class="mt-4 pt-4 border-t border-slate-200">
                        <button id="addNewTypeBtn" class="w-full text-sm font-medium text-blue-600 hover:text-blue-700 p-2 rounded-md hover:bg-blue-50 text-center">
                            + Neuen Typ hinzufügen
                        </button>
                    </div>
                </div>

                <!-- Rechte Spalte (MODIFIZIERT) -->
                <div class="lg:col-span-3 bg-white p-6 rounded-lg shadow-md border border-slate-200">
                    <div id="templateDetailView" class="hidden">
                        <div class="flex justify-between items-start mb-4">
                            <div>
                                <h2 class="text-2xl font-bold text-slate-800">Template für "<span id="selectedTypeName"></span>"</h2>
                                <div class="mt-2 border-b border-slate-200">
                                    <nav class="-mb-px flex space-x-6" aria-label="Tabs">
                                        <button id="tab-tfs" onclick="switchTemplateView('tfs')" class="tab-btn whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm">TFS Schritte</button>
                                        <button id="tab-timeline" onclick="switchTemplateView('timeline')" class="tab-btn whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm">Timeline Events</button>
                                        <button id="tab-laufkarten" onclick="switchTemplateView('laufkarten')" class="tab-btn whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm">Laufkarten</button>
                                        <button id="tab-manuals" onclick="switchTemplateView('manuals')" class="tab-btn whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm">Manuals</button>
                                    </nav>
                                </div>
                            </div>
                            <button id="deleteTypeBtn" class="text-sm font-medium text-red-600 hover:text-red-800 flex items-center gap-1">
                                <span class="material-icons-outlined text-base">delete_outline</span>
                                Diesen Typ löschen
                            </button>
                        </div>

                        <!-- Container für TFS-Schritte (bestehende Logik) -->
                        <div id="tfsStepsView">
                            <p class="text-xs text-slate-500 mb-4">Schritte per Drag & Drop sortieren. Haupt- und Unterschritte können nicht gemischt werden.</p>
                            <ul id="stepsList" class="step-list"></ul>
                            <div class="mt-6 pt-6 border-t border-slate-200">
                                <button id="addNewMainStepBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500">
                                    <span class="material-icons-outlined text-base -ml-0.5">add</span>
                                    Neuen Hauptschritt hinzufügen
                                </button>
                            </div>
                        </div>

                        <!-- NEUER Container für Timeline-Events -->
                        <div id="timelineEventsView" class="hidden">
                            <p class="text-xs text-slate-500 mb-4">Definieren Sie die Standard-Event-Abfolge für die Planungs-Timeline. Events per Drag & Drop sortieren.</p>
                            <ul id="timelineEventsList" class="space-y-3">
                                <!-- Inhalt wird per JS generiert -->
                            </ul>
                            <div class="mt-6 pt-6 border-t border-slate-200">
                                <button id="addNewTimelineEventBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-sky-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-sky-500">
                                    <span class="material-icons-outlined text-base -ml-0.5">add</span>
                                    Neues Timeline-Event hinzufügen
                                </button>
                            </div>
                        </div>

                        <!-- Container für Laufkarten -->
                        <div id="laufkartenView" class="hidden">
                            <p class="text-xs text-slate-500 mb-4">Definieren Sie, welche Laufkarten pro Zyklus für diesen Triebwerkstyp benötigt werden. Die Namen erscheinen auf der Projektseite als Eingabefelder.</p>
                            <ul id="laufkartenList" class="space-y-2">
                                <!-- Inhalt wird per JS generiert -->
                            </ul>
                            <div class="mt-6 pt-6 border-t border-slate-200">
                                <button id="addNewLaufkarteBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-violet-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-violet-500">
                                    <span class="material-icons-outlined text-base -ml-0.5">add</span>
                                    Laufkarte hinzufügen
                                </button>
                            </div>
                        </div>

                        <!-- Container für globalen Manual-Katalog -->
                        <div id="manualsView" class="hidden">
                            <p class="text-xs text-slate-500 mb-4">Globaler Katalog aller Manuals. Einträge gelten projektübergreifend und werden im Engineer Report zur Auswahl angeboten.</p>
                            <div class="overflow-x-auto">
                                <table class="w-full text-sm border-collapse" id="manualCatalogTable">
                                    <thead>
                                        <tr class="border-b border-slate-200">
                                            <th class="text-left text-xs font-semibold text-slate-500 uppercase tracking-wider pb-2 pr-3">Manualname</th>
                                            <th class="text-left text-xs font-semibold text-slate-500 uppercase tracking-wider pb-2 pr-3">Referenzcode</th>
                                            <th class="text-left text-xs font-semibold text-slate-500 uppercase tracking-wider pb-2 pr-3">Rev.</th>
                                            <th class="text-left text-xs font-semibold text-slate-500 uppercase tracking-wider pb-2 pr-3">Datum</th>
                                            <th class="text-left text-xs font-semibold text-slate-500 uppercase tracking-wider pb-2 pr-3">Engine Subtype</th>
                                            <th class="pb-2"></th>
                                        </tr>
                                    </thead>
                                    <tbody id="manualCatalogBody">
                                        <!-- Inhalt wird per JS generiert -->
                                    </tbody>
                                </table>
                            </div>
                            <div class="mt-6 pt-6 border-t border-slate-200">
                                <button id="addNewManualBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-teal-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-teal-500">
                                    <span class="material-icons-outlined text-base -ml-0.5">add</span>
                                    Manual hinzufügen
                                </button>
                            </div>
                        </div>
                    </div>

                    <div id="placeholderView" class="text-center py-16">
                        <span class="material-icons-outlined text-5xl text-slate-300">rule_folder</span>
                        <p class="mt-2 text-slate-500">Bitte wählen Sie links einen Triebwerkstyp aus.</p>
                    </div>
                </div>
            </div>
        </div>
    </main>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
    // --- Logik für die einklappbare Sidebar ---
    const sidebar = document.getElementById('sidebar');
    const sidebarToggleBtn = document.getElementById('sidebar-toggle');
    if (sidebar && sidebarToggleBtn) {
        const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
        const applyState = (isCollapsed) => {
            if (isCollapsed) {
                sidebar.classList.add('is-collapsed');
                toggleBtnIcon.textContent = 'menu';
            } else {
                sidebar.classList.remove('is-collapsed');
                toggleBtnIcon.textContent = 'chevron_left';
            }
        };
        sidebarToggleBtn.addEventListener('click', () => {
            const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
            localStorage.setItem('sidebarCollapsed', isNowCollapsed);
            applyState(isNowCollapsed);
        });
        const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
        applyState(savedState);
    }

    let templatesData = {};
    let availableDocs = [];
    let selectedType = null;
    let hasChanges = false;
    let currentView = 'tfs'; // NEU: 'tfs' oder 'timeline'

    const dom = {
        engineTypesList: document.getElementById('engineTypesList'),
        stepsList: document.getElementById('stepsList'),
        selectedTypeName: document.getElementById('selectedTypeName'),
        addNewTypeBtn: document.getElementById('addNewTypeBtn'),
        addNewMainStepBtn: document.getElementById('addNewMainStepBtn'),
        deleteTypeBtn: document.getElementById('deleteTypeBtn'),
        saveAllChangesBtn: document.getElementById('saveAllChangesBtn'),
        placeholderView: document.getElementById('placeholderView'),
        templateDetailView: document.getElementById('templateDetailView'),
        tabTfs: document.getElementById('tab-tfs'),
        tabTimeline: document.getElementById('tab-timeline'),
        tabLaufkarten: document.getElementById('tab-laufkarten'),
        tabManuals: document.getElementById('tab-manuals'),
        tfsStepsView: document.getElementById('tfsStepsView'),
        timelineEventsView: document.getElementById('timelineEventsView'),
        laufkartenView: document.getElementById('laufkartenView'),
        manualsView: document.getElementById('manualsView'),
        laufkartenList: document.getElementById('laufkartenList'),
        addNewLaufkarteBtn: document.getElementById('addNewLaufkarteBtn'),
        timelineEventsList: document.getElementById('timelineEventsList'),
        addNewTimelineEventBtn: document.getElementById('addNewTimelineEventBtn'),
    };

    let manualCatalogData = [];

    function switchTemplateView(view) {
        currentView = view;
        dom.tfsStepsView.style.display = view === 'tfs' ? 'block' : 'none';
        dom.timelineEventsView.style.display = view === 'timeline' ? 'block' : 'none';
        dom.laufkartenView.style.display = view === 'laufkarten' ? 'block' : 'none';
        dom.manualsView.style.display = view === 'manuals' ? 'block' : 'none';

        if (view === 'manuals' && manualCatalogData.length === 0) {
            loadManualCatalog();
        }

        const tabs = { tfs: dom.tabTfs, timeline: dom.tabTimeline, laufkarten: dom.tabLaufkarten, manuals: dom.tabManuals };
        Object.entries(tabs).forEach(([key, tab]) => {
            tab.classList.toggle('border-blue-600', view === key);
            tab.classList.toggle('text-blue-600', view === key);
            tab.classList.toggle('border-transparent', view !== key);
            tab.classList.toggle('text-slate-500', view !== key);
        });
    }
    window.switchTemplateView = switchTemplateView;

    async function loadManualCatalog() {
        try {
            const res = await fetch('/api/manual_catalog');
            manualCatalogData = await res.json();
        } catch (e) {
            manualCatalogData = [];
        }
        renderManualCatalogTable();
    }

    function renderManualCatalogTable() {
        const tbody = document.getElementById('manualCatalogBody');
        if (!tbody) return;
        tbody.innerHTML = '';
        if (manualCatalogData.length === 0) {
            tbody.innerHTML = '<tr><td colspan="6" class="py-6 text-center text-sm text-slate-400">Noch keine Manuals im Katalog.</td></tr>';
            return;
        }
        manualCatalogData.forEach((m, idx) => tbody.appendChild(createManualRow(m, idx)));
    }

    function createManualRow(m, idx) {
        const tr = document.createElement('tr');
        tr.className = 'manual-row border-b border-slate-100 hover:bg-slate-50';
        tr.dataset.idx = idx;
        const esc = s => (s || '').replace(/"/g, '&quot;');
        tr.innerHTML = `
            <td class="py-2 pr-3"><input type="text" class="mc-name form-input w-full text-sm" placeholder="z.B. BR700 Operations Manual" value="${esc(m.manualname)}"></td>
            <td class="py-2 pr-3"><input type="text" class="mc-ref form-input w-full text-sm font-mono" placeholder="BR700-OM-001" value="${esc(m.referenzcode)}"></td>
            <td class="py-2 pr-3"><input type="text" class="mc-rev form-input w-48 text-sm font-mono" placeholder="2026-1" value="${esc(m.revision)}"></td>
            <td class="py-2 pr-3"><input type="date" class="mc-date form-input text-sm" value="${esc(m.datum)}"></td>
            <td class="py-2 pr-3"><input type="text" class="mc-subtype form-input w-full text-sm" placeholder="z.B. BR700-715 Varianten" value="${esc(m.engine_subtype)}"></td>
            <td class="py-2 text-right"><button class="delete-manual-btn text-slate-300 hover:text-red-500 p-1 rounded hover:bg-red-50" title="Eintrag löschen"><span class="material-icons-outlined" style="font-size:18px;">delete_outline</span></button></td>
        `;
        tr.querySelector('.delete-manual-btn').addEventListener('click', () => {
            manualCatalogData.splice(idx, 1);
            renderManualCatalogTable();
        });
        return tr;
    }

    document.getElementById('addNewManualBtn').addEventListener('click', () => {
        manualCatalogData.push({ id: `m_${Date.now()}`, manualname: '', referenzcode: '', revision: '', datum: '', engine_subtype: '' });
        renderManualCatalogTable();
        const rows = document.querySelectorAll('.manual-row');
        if (rows.length) rows[rows.length - 1].querySelector('.mc-name').focus();
        markAsChanged();
    });

    function collectManualsFromDOM() {
        const tbody = document.getElementById('manualCatalogBody');
        if (!tbody) return manualCatalogData;
        const rows = tbody.querySelectorAll('.manual-row');
        const updated = [];
        rows.forEach((tr, idx) => {
            updated.push({
                id: manualCatalogData[idx]?.id || `m_${Date.now()}_${idx}`,
                manualname: tr.querySelector('.mc-name').value.trim(),
                referenzcode: tr.querySelector('.mc-ref').value.trim(),
                revision: tr.querySelector('.mc-rev').value.trim(),
                datum: tr.querySelector('.mc-date').value.trim(),
                engine_subtype: tr.querySelector('.mc-subtype').value.trim(),
            });
        });
        return updated;
    }

    async function saveManualCatalogToServer() {
        const updated = collectManualsFromDOM();
        const res = await fetch('/api/manual_catalog', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(updated)
        });
        if (!res.ok) throw new Error((await res.json()).error || 'Fehler beim Speichern der Manuals.');
        manualCatalogData = updated;
    }

    dom.stepsList.addEventListener('click', (e) => {
        // A) Unterschritt hinzufügen
        const addBtn = e.target.closest('.add-substep-btn');
        if (addBtn) {
            const mainStepContainer = addBtn.closest('.main-step-container');
            const parentId = mainStepContainer.querySelector('.main-step').dataset.id;

            let sublist = mainStepContainer.querySelector('.sub-step-list');
            if (!sublist) {
                sublist = document.createElement('ul');
                sublist.className = 'step-list sub-step-list';
                sublist.dataset.parentId = parentId;
                mainStepContainer.appendChild(sublist);
                // WICHTIG: Sortable für die neue Liste aktivieren
                initializeSortable(sublist, 'sub-steps');
            }

            const newStep = { id: `new_${Date.now()}`, parent_id: parentId, name: 'Neuer Unterschritt', document_template: null };
            sublist.appendChild(createStepElement(newStep));
            markAsChanged();
            return;
        }

        // B) Schritt löschen
        const deleteBtn = e.target.closest('.delete-step-btn');
        if (deleteBtn) {
            const wrapper = deleteBtn.closest('.step-list-item-wrapper');
            const isMain = wrapper.classList.contains('main-step');
            const message = isMain
                ? "Möchten Sie diesen Hauptschritt und alle seine Unterschritte wirklich löschen?"
                : "Möchten Sie diesen Unterschritt wirklich löschen?";

            if (confirm(message)) {
                if (isMain) {
                    wrapper.closest('.main-step-container').remove();
                } else {
                    wrapper.remove();
                }
                markAsChanged();
            }
            return;
        }

        // B2) Details-Panel (äußerer Container) ein-/ausklappen
        const detailsToggleBtn = e.target.closest('.details-toggle-btn');
        if (detailsToggleBtn) {
            const panel = detailsToggleBtn.parentElement.querySelector('.details-panel');
            const icon = detailsToggleBtn.querySelector('.details-toggle-icon');
            if (panel) {
                const isNowHidden = panel.classList.toggle('hidden');
                icon.style.transform = isNowHidden ? '' : 'rotate(90deg)';
            }
            return;
        }

        // C) Instructions-Panel ein-/ausklappen
        const toggleInstBtn = e.target.closest('.toggle-instructions-btn');
        if (toggleInstBtn) {
            const panel = toggleInstBtn.parentElement.querySelector('.instructions-panel');
            const icon = toggleInstBtn.querySelector('.inst-toggle-icon');
            if (panel) {
                const isNowHidden = panel.classList.toggle('hidden');
                icon.style.transform = isNowHidden ? '' : 'rotate(90deg)';
            }
            return;
        }

        // D) Neue Instruction hinzufügen
        const addInstBtn = e.target.closest('.add-instruction-btn');
        if (addInstBtn) {
            const panel = addInstBtn.closest('.instructions-panel');
            const container = panel.querySelector('.instruction-items-container');
            const newInst = createInstructionElement(null);
            container.appendChild(newInst);
            const section = addInstBtn.closest('.instructions-section');
            updateInstCount(section);
            markAsChanged();
            return;
        }

        // E) Instruction löschen
        const deleteInstBtn = e.target.closest('.delete-instruction-btn');
        if (deleteInstBtn) {
            const item = deleteInstBtn.closest('.instruction-item');
            const section = item.closest('.instructions-section');
            item.remove();
            if (section) updateInstCount(section);
            markAsChanged();
            return;
        }

        // F) Tools-Panel toggle
        const toggleToolsBtn = e.target.closest('.toggle-tools-btn');
        if (toggleToolsBtn) {
            const panel = toggleToolsBtn.parentElement.querySelector('.tools-panel');
            const icon = toggleToolsBtn.querySelector('.tools-toggle-icon');
            if (panel) {
                const isNowHidden = panel.classList.toggle('hidden');
                icon.style.transform = isNowHidden ? '' : 'rotate(90deg)';
            }
            return;
        }

        // G) Neues Tool hinzufügen
        const addToolBtn = e.target.closest('.add-tool-btn');
        if (addToolBtn) {
            const panel = addToolBtn.closest('.tools-panel');
            const container = panel.querySelector('.tool-items-container');
            const newTool = createToolElement(null);
            container.appendChild(newTool);
            updateToolIndices(container);
            const section = addToolBtn.closest('.tools-section');
            updateToolCount(section);
            markAsChanged();
            return;
        }

        // H) Tool löschen
        const deleteToolBtn = e.target.closest('.delete-tool-btn');
        if (deleteToolBtn) {
            const item = deleteToolBtn.closest('.tool-item');
            const container = item.closest('.tool-items-container');
            const section = item.closest('.tools-section');
            item.remove();
            updateToolIndices(container);
            if (section) updateToolCount(section);
            markAsChanged();
            return;
        }
    });

    // 2. Input-Handler für Textfelder (Änderungen erkennen)
    dom.stepsList.addEventListener('input', (e) => {
        if (e.target.classList.contains('step-name-input') || e.target.classList.contains('document-template-select')) {
            markAsChanged();
        }
    });
    
    async function fetchData() {
        try {
            const [templatesRes, docsRes] = await Promise.all([
                fetch("{{ url_for('api_get_process_templates') }}"),
                fetch("{{ url_for('api_get_available_documents') }}")
            ]);
            if (!templatesRes.ok || !docsRes.ok) throw new Error('Fehler beim Laden der Konfigurationsdaten.');

            templatesData = await templatesRes.json();
            availableDocs = await docsRes.json();
            migrateDataToHierarchical();
            renderTypeList();
            renderViews(); // Ersetzt den Aufruf an renderStepList
            updateSaveButtonState();
        } catch (error) { console.error(error); alert('Fehler: ' + error.message); }
    }

    function migrateDataToHierarchical() {
        Object.keys(templatesData).forEach(type => {
            const steps = templatesData[type].steps;
            if (steps.length > 0 && typeof steps[0].id === 'undefined') {
                console.log(`Migriere Daten für Typ: ${type}`);
                templatesData[type].steps = steps.map((step, index) => ({
                    id: String(index + 1), parent_id: null, name: step.name,
                    document_template: step.document_template
                }));
            }
        });
    }

    function renderTypeList() {
        dom.engineTypesList.innerHTML = '';
        Object.keys(templatesData).sort().forEach(type => {
            const li = document.createElement('li');
            li.className = 'type-list-item cursor-pointer p-2 rounded-md text-sm';
            if (type === selectedType) li.classList.add('selected');
            li.textContent = type;
            li.dataset.type = type;
            li.addEventListener('click', () => {
                if (hasChanges && !confirm("Sie haben ungespeicherte Änderungen. Wirklich den Typ wechseln? Die Änderungen gehen verloren.")) {
                    return;
                }
                selectedType = type; hasChanges = false;
                // Wichtig: Beim Wechsel immer auf die erste Ansicht zurücksetzen
                currentView = 'tfs';
                fetchData();
            });
            dom.engineTypesList.appendChild(li);
        });
    }

    function renderViews() {
        if (!selectedType || !templatesData[selectedType]) {
            dom.placeholderView.style.display = 'block';
            dom.templateDetailView.style.display = 'none';
            return;
        }

        dom.placeholderView.style.display = 'none';
        dom.templateDetailView.style.display = 'block';
        dom.selectedTypeName.textContent = selectedType;
        dom.deleteTypeBtn.style.display = selectedType === 'default' ? 'none' : 'block';

        renderStepList();
        renderTimelineEventsList();
        renderLaufkartenList();

        switchTemplateView(currentView);
    }

    function renderStepList() {
        dom.stepsList.innerHTML = '';
        const steps = templatesData[selectedType].steps;
        const stepMap = new Map(steps.map(s => [s.id, { ...s, children: [] }]));
        const rootSteps = [];
        steps.forEach(s => {
            if (s.parent_id && stepMap.has(s.parent_id)) {
                stepMap.get(s.parent_id).children.push(stepMap.get(s.id));
            } else if (!s.parent_id) {
                rootSteps.push(stepMap.get(s.id));
            }
        });
        rootSteps.forEach(step => {
            const mainStepWrapper = document.createElement('li');
            mainStepWrapper.className = 'main-step-container';
            mainStepWrapper.appendChild(createStepElement(step));
            if (step.children.length > 0) {
                const sublist = document.createElement('ul');
                sublist.className = 'step-list sub-step-list';
                sublist.dataset.parentId = step.id;
                step.children.forEach(child => sublist.appendChild(createStepElement(child)));
                mainStepWrapper.appendChild(sublist);
            }
            dom.stepsList.appendChild(mainStepWrapper);
        });
        initializeSortable(dom.stepsList, 'main-steps');
        document.querySelectorAll('.sub-step-list').forEach(sublist => {
            initializeSortable(sublist, 'sub-steps');
        });
    }

    function renderTimelineEventsList() {
        dom.timelineEventsList.innerHTML = '';
        if (!selectedType || !templatesData[selectedType]) return;

        const timelineEvents = templatesData[selectedType].timeline_events || [];

        if (timelineEvents.length === 0) {
            dom.timelineEventsList.innerHTML = '<p class="text-sm text-center text-slate-400 py-4">Noch keine Timeline-Events definiert.</p>';
        } else {
            timelineEvents.forEach(event => {
                dom.timelineEventsList.appendChild(createTimelineEventElement(event));
            });
        }

        Sortable.create(dom.timelineEventsList, {
            animation: 150,
            handle: '.drag-handle',
            ghostClass: 'sortable-ghost',
            onEnd: () => markAsChanged()
        });
        attachTimelineEventListeners();
    }

    function createTimelineEventElement(event) {
        const li = document.createElement('li');
        li.className = 'timeline-event-item bg-white border rounded-lg p-3';

        const assigns = event.assigns || { people: true, rooms: false, hardware: false };
        const releases = event.releases || { hardware: false };
        const rawColor = event.color || '#9CA3AF';
        const colorVal = rawColor.startsWith('#') ? rawColor : '#9CA3AF';

        li.innerHTML = `
            <div class="flex items-center gap-2">
                <span class="material-icons-outlined drag-handle flex-shrink-0 cursor-grab text-slate-400">drag_indicator</span>
                <input type="text" class="form-input flex-1 min-w-0 event-name-input" placeholder="Event Name" value="${event.name || ''}" />
                <div class="flex items-center gap-1 flex-shrink-0">
                    <input type="number" min="1" class="form-input w-14 text-center event-duration-input" title="Dauer in Schichten" value="${event.duration_shifts || 1}" />
                    <span class="text-xs text-slate-400 whitespace-nowrap">Sch.</span>
                </div>
                <input type="color" class="h-9 w-9 flex-shrink-0 cursor-pointer rounded-md border border-slate-300 p-0.5 event-color-input" title="Farbe" value="${colorVal}" />
                <button class="action-button delete-event-btn flex-shrink-0" title="Dieses Event löschen">
                    <span class="material-icons-outlined">delete_outline</span>
                </button>
            </div>
            <div class="mt-2 ml-8 flex flex-wrap items-center gap-x-5 gap-y-1 text-xs text-slate-600 border-t border-slate-100 pt-2">
                <span class="font-semibold text-slate-400 uppercase tracking-wide text-[10px]">Zuweisen:</span>
                <label class="flex items-center gap-1.5 cursor-pointer">
                    <input type="checkbox" class="assigns-people-input rounded" ${assigns.people ? 'checked' : ''}>
                    <span>Mitarbeiter</span>
                </label>
                <label class="flex items-center gap-1.5 cursor-pointer">
                    <input type="checkbox" class="assigns-rooms-input rounded" ${assigns.rooms ? 'checked' : ''}>
                    <span>Räume (Testzellen)</span>
                </label>
                <label class="flex items-center gap-1.5 cursor-pointer">
                    <input type="checkbox" class="assigns-hardware-input rounded" ${assigns.hardware ? 'checked' : ''}>
                    <span>Hardware-Kit</span>
                </label>
                <span class="ml-2 font-semibold text-slate-400 uppercase tracking-wide text-[10px]">Freigeben:</span>
                <label class="flex items-center gap-1.5 cursor-pointer">
                    <input type="checkbox" class="releases-hardware-input rounded" ${releases.hardware ? 'checked' : ''}>
                    <span>Hardware-Kit</span>
                </label>
            </div>
        `;
        return li;
    }

    function attachTimelineEventListeners() {
        dom.timelineEventsList.querySelectorAll('.event-name-input, .event-duration-input, .event-color-input').forEach(el => {
            el.addEventListener('input', () => markAsChanged());
        });
        dom.timelineEventsList.querySelectorAll('.assigns-people-input, .assigns-rooms-input').forEach(el => {
            el.addEventListener('change', () => markAsChanged());
        });
        dom.timelineEventsList.querySelectorAll('.delete-event-btn').forEach(btn => {
            btn.addEventListener('click', (ev) => {
                if (confirm("Möchten Sie dieses Timeline-Event wirklich löschen?")) {
                    ev.currentTarget.closest('.timeline-event-item').remove();
                    markAsChanged();
                }
            });
        });
    }

    function renderLaufkartenList() {
        dom.laufkartenList.innerHTML = '';
        if (!selectedType || !templatesData[selectedType]) return;
        const laufkarten = templatesData[selectedType].laufkarten || [];
        if (laufkarten.length === 0) {
            dom.laufkartenList.innerHTML = '<p class="text-sm text-center text-slate-400 py-4">Noch keine Laufkarten definiert.</p>';
        } else {
            laufkarten.forEach(lk => dom.laufkartenList.appendChild(createLaufkarteElement(lk)));
        }
    }

    function createLaufkarteElement(lk) {
        const li = document.createElement('li');
        li.className = 'laufkarte-item flex items-center gap-3 bg-white border border-slate-200 rounded-lg px-3 py-2';
        li.dataset.id = lk.id || `lk_${Date.now()}`;
        li.innerHTML = `
            <span class="material-icons-outlined text-violet-400 flex-shrink-0" style="font-size:18px;">assignment</span>
            <input type="text" class="lk-name-input form-input flex-1 text-sm" placeholder="Name der Laufkarte (z.B. Einbau-Laufkarte)" value="${(lk.name || '').replace(/"/g,'&quot;')}" />
            <button class="delete-lk-btn text-slate-400 hover:text-red-500 p-1 rounded hover:bg-red-50 flex-shrink-0" title="Laufkarte entfernen">
                <span class="material-icons-outlined" style="font-size:18px;">delete_outline</span>
            </button>
        `;
        li.querySelector('.lk-name-input').addEventListener('input', () => markAsChanged());
        li.querySelector('.delete-lk-btn').addEventListener('click', () => {
            li.remove();
            if (!dom.laufkartenList.querySelector('.laufkarte-item')) {
                dom.laufkartenList.innerHTML = '<p class="text-sm text-center text-slate-400 py-4">Noch keine Laufkarten definiert.</p>';
            }
            markAsChanged();
        });
        return li;
    }

    dom.addNewLaufkarteBtn.addEventListener('click', () => {
        if (!selectedType) return;
        const emptyMsg = dom.laufkartenList.querySelector('p');
        if (emptyMsg) emptyMsg.remove();
        dom.laufkartenList.appendChild(createLaufkarteElement({ id: `lk_${Date.now()}`, name: '' }));
        markAsChanged();
    });

    function createStepElement(step) {
        const isMainStep = !step.parent_id;
        const docOptions = ['<option value="">-- Kein Dokument --</option>', ...availableDocs.map(doc => `<option value="${doc}" ${step.document_template === doc ? 'selected' : ''}>${doc}</option>`)].join('');
        const li = document.createElement('li');
        li.className = `step-list-item-wrapper ${isMainStep ? 'main-step' : 'sub-step'}`;
        li.dataset.id = step.id;
        const addSubstepButtonHtml = isMainStep ? `
            <button class="action-button add-substep-btn" title="Unterschritt hinzufügen">
                <span class="material-icons-outlined">add_circle_outline</span>
            </button>
        ` : `<div class="w-10"></div>`;

        const instCount = !isMainStep ? (step.instructions || []).length : 0;
        const toolCount = !isMainStep ? (step.tools || []).length : 0;
        const instructionsSectionHtml = !isMainStep ? `
            <div class="details-section ml-10 mt-1.5">
                <button type="button" class="details-toggle-btn w-full flex items-center gap-1.5 text-xs text-slate-500 hover:text-slate-700 py-1.5 px-2 bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded transition-colors">
                    <span class="material-icons-outlined details-toggle-icon" style="font-size:15px;">chevron_right</span>
                    <span class="details-summary-label">${toolCount} Werkzeug${toolCount !== 1 ? 'e' : ''} · 0 Dok · ${instCount} Instruction${instCount !== 1 ? 's' : ''}</span>
                </button>
                <div class="details-panel hidden mt-1 border border-slate-200 rounded bg-white divide-y divide-slate-100">
                    <div class="tools-section p-1.5">
                        <button type="button" class="toggle-tools-btn flex items-center gap-1 text-xs text-amber-600 hover:text-amber-800 py-1 px-1">
                            <span class="material-icons-outlined tools-toggle-icon" style="font-size:15px;">chevron_right</span>
                            <span class="material-icons-outlined text-[12px]">build</span>
                            <span class="tools-count-label">${toolCount} Werkzeug${toolCount !== 1 ? 'e' : ''}</span>
                        </button>
                        <div class="tools-panel hidden mt-1.5 space-y-1 pl-1">
                            <div class="tool-items-container space-y-1"></div>
                            <button type="button" class="add-tool-btn flex items-center gap-0.5 text-xs font-medium text-amber-700 hover:text-amber-900 mt-1 px-1 py-0.5 rounded hover:bg-amber-50">
                                <span class="material-icons-outlined" style="font-size:15px;">add</span> Werkzeug / Material hinzufügen
                            </button>
                        </div>
                    </div>
                    <div class="references-section p-1.5">
                        <button type="button" class="toggle-references-btn flex items-center gap-1 text-xs text-violet-600 hover:text-violet-800 py-1 px-1">
                            <span class="material-icons-outlined refs-toggle-icon" style="font-size:15px;">chevron_right</span>
                            <span class="material-icons-outlined text-[12px]">library_books</span>
                            <span class="refs-count-label">0 Applicable Documents</span>
                        </button>
                        <div class="references-panel hidden mt-1.5 space-y-1 pl-1">
                            <div class="reference-items-container space-y-1.5"></div>
                            <button type="button" class="add-reference-btn flex items-center gap-0.5 text-xs font-medium text-violet-700 hover:text-violet-900 mt-1 px-1 py-0.5 rounded hover:bg-violet-50">
                                <span class="material-icons-outlined" style="font-size:15px;">add</span> Referenz hinzufügen
                            </button>
                        </div>
                    </div>
                    <div class="instructions-section p-1.5">
                        <button type="button" class="toggle-instructions-btn flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 py-1 px-1">
                            <span class="material-icons-outlined inst-toggle-icon" style="font-size:15px;">chevron_right</span>
                            <span class="inst-count-label">${instCount} Instruction${instCount !== 1 ? 's' : ''}</span>
                        </button>
                        <div class="instructions-panel hidden mt-1.5 space-y-1 pl-1">
                            <div class="instruction-items-container space-y-1"></div>
                            <button type="button" class="add-instruction-btn flex items-center gap-0.5 text-xs font-medium text-green-700 hover:text-green-900 mt-1 px-1 py-0.5 rounded hover:bg-green-50">
                                <span class="material-icons-outlined" style="font-size:15px;">add</span> Instruction hinzufügen
                            </button>
                        </div>
                    </div>
                </div>
            </div>` : '';

        li.innerHTML = `
            <div class="step-list-item">
                <span class="material-icons-outlined drag-handle">drag_indicator</span>
                <div>
                    <label class="block text-xs font-medium text-slate-500">Schrittname</label>
                    <input type="text" class="form-input mt-1 step-name-input" value="${step.name}" />
                </div>
                <div>
                    <label class="block text-xs font-medium text-slate-500">Dokument-Template</label>
                    <select class="form-select mt-1 document-template-select">${docOptions}</select>
                </div>
                ${addSubstepButtonHtml}
                <button class="action-button delete-step-btn" title="Diesen Schritt löschen">
                    <span class="material-icons-outlined">delete_outline</span>
                </button>
            </div>
            ${instructionsSectionHtml}
        `;

        // Für Unterschritte: vorhandene Tools + Instructions + References rendern
        if (!isMainStep) {
            const toolsContainer = li.querySelector('.tool-items-container');
            (step.tools || []).forEach(tool => toolsContainer.appendChild(createToolElement(tool)));
            updateToolIndices(toolsContainer);

            const itemsContainer = li.querySelector('.instruction-items-container');
            (step.instructions || []).forEach(inst => itemsContainer.appendChild(createInstructionElement(inst)));
            Sortable.create(itemsContainer, {
                animation: 150, handle: '.inst-drag-handle', ghostClass: 'sortable-ghost', onEnd: () => markAsChanged()
            });

            // References
            const refsSection = li.querySelector('.references-section');
            const refsContainer = li.querySelector('.reference-items-container');
            const refsPanel = li.querySelector('.references-panel');
            const refsToggleBtn = li.querySelector('.toggle-references-btn');
            const refsToggleIcon = li.querySelector('.refs-toggle-icon');
            const addRefBtn = li.querySelector('.add-reference-btn');

            (step.references || []).forEach(ref => refsContainer.appendChild(createReferenceElement(ref)));
            updateRefsCount(refsSection);

            const detailsSection = li.querySelector('.details-section');
            if (detailsSection) updateDetailsSummary(detailsSection);

            refsToggleBtn.addEventListener('click', () => {
                const isHidden = refsPanel.classList.toggle('hidden');
                refsToggleIcon.textContent = isHidden ? 'chevron_right' : 'expand_more';
            });
            addRefBtn.addEventListener('click', () => {
                refsContainer.appendChild(createReferenceElement(null));
                if (refsPanel.classList.contains('hidden')) { refsPanel.classList.remove('hidden'); refsToggleIcon.textContent = 'expand_more'; }
                updateRefsCount(refsSection);
                markAsChanged();
            });

        }

        return li;
    }

    function updateDetailsSummary(detailsSection) {
        const summary = detailsSection.querySelector('.details-summary-label');
        if (!summary) return;
        const toolCount = detailsSection.querySelectorAll('.tool-item').length;
        const refsCount = detailsSection.querySelectorAll('.reference-item').length;
        const instCount = detailsSection.querySelectorAll('.instruction-item').length;
        summary.textContent = `${toolCount} Werkzeug${toolCount !== 1 ? 'e' : ''} · ${refsCount} Dok · ${instCount} Instruction${instCount !== 1 ? 's' : ''}`;
    }

    function updateInstCount(section) {
        const container = section.querySelector('.instruction-items-container');
        const label = section.querySelector('.inst-count-label');
        if (!container || !label) return;
        const count = container.querySelectorAll('.instruction-item').length;
        label.textContent = `${count} Instruction${count !== 1 ? 's' : ''}`;
        const ds = section.closest('.details-section');
        if (ds) updateDetailsSummary(ds);
    }

    function updateToolIndices(container) {
        container.querySelectorAll('.tool-item').forEach((item, idx) => {
            const el = item.querySelector('.tool-index');
            if (el) el.textContent = `(${idx + 1})`;
        });
    }

    function updateToolCount(section) {
        const container = section.querySelector('.tool-items-container');
        const label = section.querySelector('.tools-count-label');
        if (!container || !label) return;
        const count = container.querySelectorAll('.tool-item').length;
        label.textContent = `${count} Werkzeug${count !== 1 ? 'e' : ''}`;
        const ds = section.closest('.details-section');
        if (ds) updateDetailsSummary(ds);
    }

    function createToolElement(tool) {
        const type = tool ? (tool.type || 'tooling') : 'tooling';
        const label = tool ? (tool.label || '') : '';
        const hwUuid = tool ? (tool.hw_uuid || '') : '';
        const quantity = tool ? (tool.quantity || '') : '';

        const div = document.createElement('div');
        div.className = 'tool-item flex items-center gap-2 p-2 bg-white border border-slate-200 rounded-lg';
        div.innerHTML = `
            <span class="tool-index text-xs font-mono font-bold text-blue-600 w-6 flex-shrink-0 text-center"></span>
            <select class="tool-type-select form-select text-xs py-1 flex-shrink-0" style="width:125px">
                <option value="tooling" ${type==='tooling'?'selected':''}>🔧 Tooling</option>
                <option value="consumable" ${type==='consumable'?'selected':''}>💧 Consumable</option>
            </select>
            <div class="flex-1 flex flex-col gap-1 min-w-0 relative">
                <div class="flex gap-1">
                    <input type="text" class="tool-label-input form-input text-xs py-1 flex-1" placeholder="Bezeichnung..." value="${label.replace(/"/g,'&quot;')}">
                    <button type="button" class="tool-hw-search-btn flex-shrink-0 flex items-center gap-1 text-xs px-2 py-1 bg-slate-50 border border-slate-300 rounded hover:bg-blue-50 hover:border-blue-400 transition-colors" title="Aus Inventar wählen">
                        <span class="material-icons-outlined text-[13px]">inventory_2</span>
                    </button>
                </div>
                <div class="tool-hw-dropdown hidden absolute top-full left-0 right-0 z-50 mt-0.5">
                    <div class="bg-white border border-slate-200 rounded shadow-lg">
                        <input type="text" class="tool-hw-search-input w-full text-xs px-2 py-1.5 border-b border-slate-100 outline-none rounded-t" placeholder="Hardware suchen...">
                        <ul class="tool-hw-results max-h-36 overflow-y-auto text-xs"></ul>
                    </div>
                </div>
            </div>
            <input type="text" class="tool-quantity-input form-input text-xs py-1 flex-shrink-0" style="width:78px" placeholder="Menge" value="${quantity}">
            <button type="button" class="delete-tool-btn flex-shrink-0 text-slate-300 hover:text-red-500 p-0.5" title="Löschen">
                <span class="material-icons-outlined" style="font-size:16px;">close</span>
            </button>
        `;

        const labelInput = div.querySelector('.tool-label-input');
        labelInput.dataset.hwUuid = hwUuid;
        labelInput.addEventListener('input', () => markAsChanged());
        div.querySelector('.tool-type-select').addEventListener('change', () => markAsChanged());
        div.querySelector('.tool-quantity-input').addEventListener('input', () => markAsChanged());

        const searchBtn = div.querySelector('.tool-hw-search-btn');
        const dropdown = div.querySelector('.tool-hw-dropdown');
        const searchInput = div.querySelector('.tool-hw-search-input');
        const resultsList = div.querySelector('.tool-hw-results');

        function renderToolHwResults(items, query) {
            const filtered = query ? items.filter(i => i.label.toLowerCase().includes(query.toLowerCase()) || i.cleanLabel.toLowerCase().includes(query.toLowerCase())) : items;
            resultsList.innerHTML = filtered.slice(0, 50).map(i =>
                `<li class="px-3 py-1.5 hover:bg-blue-50 cursor-pointer flex items-center gap-1.5 border-b border-slate-50 last:border-0" data-clean-label="${i.cleanLabel.replace(/"/g,'&quot;')}" data-hw-uuid="${i.hw_uuid}">
                    <span class="material-icons-outlined text-[11px] text-blue-400">inventory_2</span>
                    <span class="flex-1 truncate">${i.cleanLabel}</span>
                    <span class="text-[10px] text-slate-400 shrink-0">${i.pn}</span>
                </li>`
            ).join('') || '<li class="px-3 py-2 text-slate-400">Keine Treffer</li>';
            resultsList.querySelectorAll('li[data-hw-uuid]').forEach(li => {
                li.addEventListener('click', () => {
                    labelInput.value = li.dataset.cleanLabel;
                    labelInput.dataset.hwUuid = li.dataset.hwUuid;
                    dropdown.classList.add('hidden');
                    markAsChanged();
                });
            });
        }

        searchBtn.addEventListener('click', async e => {
            e.stopPropagation();
            dropdown.classList.toggle('hidden');
            if (!dropdown.classList.contains('hidden')) {
                const items = await fetchHwInventory();
                renderToolHwResults(items, '');
                searchInput.focus();
            }
        });

        searchInput.addEventListener('input', async () => {
            const items = await fetchHwInventory();
            renderToolHwResults(items, searchInput.value);
        });

        document.addEventListener('click', e => {
            if (!dropdown.contains(e.target) && e.target !== searchBtn) dropdown.classList.add('hidden');
        }, {capture: true});

        return div;
    }

    let _hwInventoryCache = null;
    async function fetchHwInventory() {
        if (_hwInventoryCache) return _hwInventoryCache;
        try {
            const res = await fetch('/api/test_equipment/data');
            const data = await res.json();
            _hwInventoryCache = (Array.isArray(data) ? data : []).map(item => {
                const m = item.label.match(/\((.+)\)/);
                const cleanLabel = m ? m[1] : item.label;
                return { label: item.label, cleanLabel, hw_uuid: item.id, pn: item.pn || '' };
            }).sort((a,b) => a.label.localeCompare(b.label));
        } catch(e) { _hwInventoryCache = []; }
        return _hwInventoryCache;
    }

    function renderInstChips(chipsContainer, optObjs, onChange) {
        chipsContainer.innerHTML = '';
        optObjs.forEach(opt => {
            const chip = document.createElement('span');
            chip.className = `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border ${opt.hw_uuid ? 'bg-blue-50 text-blue-800 border-blue-200' : 'bg-slate-100 text-slate-700 border-slate-200'}`;
            chip.dataset.label = opt.label;
            if (opt.hw_uuid) chip.dataset.hwUuid = opt.hw_uuid;
            const icon = opt.hw_uuid ? '<span class="material-icons-outlined text-[10px] text-blue-400">inventory_2</span>' : '';
            chip.innerHTML = `${icon}<span>${opt.label}</span><button type="button" class="ml-0.5 leading-none text-slate-300 hover:text-red-500 font-bold">×</button>`;
            chip.querySelector('button').onclick = e => { e.stopPropagation(); chip.remove(); onChange(); };
            chipsContainer.appendChild(chip);
        });
    }

    function createInstructionElement(inst) {
        const type = inst ? (inst.type || 'normal') : 'normal';
        const text = inst ? (inst.text || '') : '';
        const placeholder = inst ? (inst.placeholder || '') : '';
        const optObjs = inst && Array.isArray(inst.options)
            ? inst.options.map(o => typeof o === 'string' ? {label: o} : o)
            : [];

        const div = document.createElement('div');
        div.className = 'instruction-item';
        div.innerHTML = `
            <span class="material-icons-outlined inst-drag-handle flex-shrink-0 cursor-grab text-slate-300 mt-1" style="font-size:18px;">drag_indicator</span>
            <select class="inst-type-select form-select text-xs py-1 flex-shrink-0" style="width:140px;">
                <option value="normal" ${type==='normal'?'selected':''}>Normal</option>
                <option value="caution" ${type==='caution'?'selected':''}>Caution ⚠</option>
                <option value="warning" ${type==='warning'?'selected':''}>Warning 🚫</option>
                <option value="part_selection" ${type==='part_selection'?'selected':''}>Part Selection</option>
                <option value="text_input" ${type==='text_input'?'selected':''}>Text Input</option>
            </select>
            <div style="flex:1; min-width:0;" class="space-y-1">
                <div class="flex gap-1 relative">
                    <input type="text" class="inst-text-input form-input text-xs py-1 flex-1" placeholder="Beschreibung der Arbeitsanweisung...">
                    <button type="button" class="inst-tool-ref-btn flex-shrink-0 flex items-center gap-0.5 text-xs px-1.5 py-1 bg-slate-50 border border-slate-200 rounded hover:bg-blue-50 hover:border-blue-300 transition-colors" title="Tool-Referenz (n) einfügen">
                        <span class="material-icons-outlined text-[13px] text-slate-400">build</span>
                    </button>
                    <button type="button" class="inst-ref-insert-btn flex-shrink-0 flex items-center gap-0.5 text-xs px-1.5 py-1 bg-slate-50 border border-slate-200 rounded hover:bg-violet-50 hover:border-violet-300 transition-colors" title="[[REF:...]] einfügen">
                        <span class="material-icons-outlined text-[13px] text-violet-400">library_books</span>
                    </button>
                    <button type="button" class="inst-img-insert-btn flex-shrink-0 flex items-center gap-0.5 text-xs px-1.5 py-1 bg-slate-50 border border-slate-200 rounded hover:bg-blue-50 hover:border-blue-300 transition-colors" title="[[IMAGE:...]] einfügen">
                        <span class="material-icons-outlined text-[13px] text-blue-400">image</span>
                    </button>
                    <div class="inst-tool-ref-dropdown hidden absolute top-full right-0 z-50 mt-0.5 min-w-[200px]">
                        <ul class="inst-tool-ref-list bg-white border border-slate-200 rounded shadow-lg text-xs max-h-40 overflow-y-auto"></ul>
                    </div>
                    <div class="inst-ref-insert-dropdown hidden absolute top-full right-0 z-50 mt-0.5 w-72">
                        <div class="bg-white border border-violet-200 rounded shadow-lg p-2 space-y-1.5">
                            <p class="text-[10px] font-bold text-violet-700 uppercase tracking-wider px-1">Dokument-Referenz einfügen</p>
                            <div class="flex gap-1">
                                <input type="text" list="ref-doc-types" class="inst-ref-type-input form-input text-xs py-1 flex-shrink-0 font-mono font-bold uppercase" style="width:72px" placeholder="AMM…">
                                <input type="text" class="inst-ref-num-input form-input text-xs py-1 flex-1" placeholder="z.B. 72-10-01">
                            </div>
                            <button type="button" class="inst-ref-confirm-btn w-full text-xs font-semibold text-white bg-violet-600 hover:bg-violet-700 rounded py-1">Einfügen</button>
                        </div>
                    </div>
                    <div class="inst-img-insert-dropdown hidden absolute top-full right-0 z-50 mt-0.5 w-72">
                        <div class="bg-white border border-blue-200 rounded shadow-lg p-2 space-y-1.5">
                            <p class="text-[10px] font-bold text-blue-700 uppercase tracking-wider px-1">Bild-Referenz einfügen</p>
                            <div class="inst-img-pick-phase space-y-1.5">
                                <ul class="inst-img-list max-h-36 overflow-y-auto text-xs border border-slate-100 rounded divide-y divide-slate-50"></ul>
                                <div class="pt-1 border-t border-slate-100">
                                    <label class="flex items-center gap-1.5 cursor-pointer text-xs text-blue-600 hover:text-blue-800">
                                        <span class="material-icons-outlined text-[14px]">upload</span>Bild hochladen
                                        <input type="file" class="inst-img-upload-input hidden" accept=".png,.jpg,.jpeg,.gif,.webp,.svg">
                                    </label>
                                </div>
                            </div>
                            <div class="inst-img-label-phase hidden space-y-1.5">
                                <div class="flex items-center gap-1 text-[10px] text-blue-600 cursor-pointer inst-img-back-btn">
                                    <span class="material-icons-outlined text-[13px]">arrow_back</span>zurück
                                </div>
                                <p class="text-[10px] text-slate-500 px-0.5 inst-img-selected-name font-mono truncate"></p>
                                <input type="text" class="inst-img-label-input form-input text-xs py-1 w-full" placeholder="Kurzbeschreibung (optional), z.B. Flansch-Detail">
                                <button type="button" class="inst-img-confirm-btn w-full text-xs font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded py-1">Einfügen</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="inst-text-input-wrapper ${type==='text_input'?'':'hidden'} bg-slate-50 border border-slate-200 rounded p-2 space-y-1.5">
                    <p class="text-[10px] font-semibold text-slate-500 uppercase tracking-wider">Platzhaltertext (optional)</p>
                    <input type="text" class="inst-placeholder-input form-input text-xs py-1 w-full" placeholder="z.B. NEUTRAL, N/A, Laufnummer...">
                </div>
                <div class="inst-options-wrapper ${type==='part_selection'?'':'hidden'} bg-slate-50 border border-slate-200 rounded p-2 space-y-1.5">
                    <p class="text-[10px] font-semibold text-slate-500 uppercase tracking-wider">Auswahloptionen für den Mechaniker</p>
                    <div class="inst-hw-chips flex flex-wrap gap-1 min-h-[24px]"></div>
                    <div class="flex gap-1">
                        <input type="text" class="inst-free-text-input form-input text-xs py-1 flex-1" placeholder="Neue Option... + Enter">
                        <button type="button" class="inst-hw-search-btn flex-shrink-0 flex items-center gap-1 text-xs px-2 py-1 bg-white border border-slate-300 rounded hover:bg-blue-50 hover:border-blue-400 transition-colors">
                            <span class="material-icons-outlined text-[13px]">inventory_2</span>Inventar
                        </button>
                    </div>
                    <div class="inst-hw-dropdown hidden relative">
                        <div class="absolute top-0 left-0 right-0 bg-white border border-slate-200 rounded shadow-lg z-50">
                            <input type="text" class="inst-hw-search-input w-full text-xs px-2 py-1.5 border-b border-slate-100 outline-none rounded-t" placeholder="Hardware suchen...">
                            <ul class="inst-hw-results max-h-36 overflow-y-auto text-xs"></ul>
                        </div>
                    </div>
                </div>
            </div>
            <button type="button" class="delete-instruction-btn flex-shrink-0 text-slate-300 hover:text-red-500 mt-0.5 p-0.5" title="Löschen">
                <span class="material-icons-outlined" style="font-size:16px;">close</span>
            </button>
        `;

        div.querySelector('.inst-text-input').value = text;
        div.querySelector('.inst-placeholder-input').value = placeholder;

        // Tool-Referenz Insert-Button
        const toolRefBtn = div.querySelector('.inst-tool-ref-btn');
        const toolRefDropdown = div.querySelector('.inst-tool-ref-dropdown');
        const toolRefList = div.querySelector('.inst-tool-ref-list');
        const textInput = div.querySelector('.inst-text-input');
        toolRefBtn.addEventListener('click', e => {
            e.stopPropagation();
            toolRefDropdown.classList.toggle('hidden');
            if (!toolRefDropdown.classList.contains('hidden')) {
                const toolsContainer = div.closest('.instructions-section')
                    ?.closest('li')?.querySelector('.tool-items-container');
                const tools = toolsContainer ? [...toolsContainer.querySelectorAll('.tool-item')] : [];
                if (!tools.length) {
                    toolRefList.innerHTML = '<li class="px-3 py-2 text-slate-400 italic">Keine Werkzeuge definiert</li>';
                } else {
                    toolRefList.innerHTML = tools.map((t, idx) => {
                        const label = t.querySelector('.tool-label-input')?.value || '';
                        return `<li class="px-3 py-1.5 hover:bg-blue-50 cursor-pointer flex items-center gap-2 border-b border-slate-50 last:border-0" data-ref="(${idx+1})">
                            <span class="font-mono font-bold text-blue-600 text-[11px]">(${idx+1})</span>
                            <span class="truncate">${label}</span>
                        </li>`;
                    }).join('');
                    toolRefList.querySelectorAll('li[data-ref]').forEach(li => {
                        li.addEventListener('click', () => {
                            const ref = li.dataset.ref;
                            const pos = textInput.selectionStart;
                            const val = textInput.value;
                            textInput.value = val.slice(0, pos) + ref + val.slice(pos);
                            textInput.focus();
                            textInput.selectionStart = textInput.selectionEnd = pos + ref.length;
                            toolRefDropdown.classList.add('hidden');
                            markAsChanged();
                        });
                    });
                }
            }
        });
        document.addEventListener('click', e => {
            if (!toolRefDropdown.contains(e.target) && e.target !== toolRefBtn) toolRefDropdown.classList.add('hidden');
        }, {capture: true});

        // --- [[REF:...]] Insert Button ---
        const refInsertBtn = div.querySelector('.inst-ref-insert-btn');
        const refInsertDropdown = div.querySelector('.inst-ref-insert-dropdown');
        const refTypeInput = div.querySelector('.inst-ref-type-input');
        const refNumInput = div.querySelector('.inst-ref-num-input');
        const refConfirmBtn = div.querySelector('.inst-ref-confirm-btn');

        refInsertBtn.addEventListener('click', e => {
            e.stopPropagation();
            div.querySelector('.inst-img-insert-dropdown').classList.add('hidden');
            toolRefDropdown.classList.add('hidden');
            refInsertDropdown.classList.toggle('hidden');
            if (!refInsertDropdown.classList.contains('hidden')) refTypeInput.focus();
        });
        refConfirmBtn.addEventListener('click', () => {
            const type = refTypeInput.value.trim().toUpperCase();
            const num  = refNumInput.value.trim();
            if (!type && !num) return;
            const tag = `[[REF:${type}${num ? ' ' + num : ''}]]`;
            const pos = textInput.selectionStart || textInput.value.length;
            textInput.value = textInput.value.slice(0, pos) + tag + textInput.value.slice(pos);
            textInput.focus();
            refInsertDropdown.classList.add('hidden');
            refTypeInput.value = ''; refNumInput.value = '';
            markAsChanged();
        });
        refNumInput.addEventListener('keydown', e => { if (e.key === 'Enter') refConfirmBtn.click(); });
        document.addEventListener('click', e => {
            if (!refInsertDropdown.contains(e.target) && e.target !== refInsertBtn) refInsertDropdown.classList.add('hidden');
        }, {capture: true});

        // --- [[IMAGE:...]] Insert Button ---
        const imgInsertBtn  = div.querySelector('.inst-img-insert-btn');
        const imgDropdown   = div.querySelector('.inst-img-insert-dropdown');
        const imgList       = div.querySelector('.inst-img-list');
        const imgUploadInput = div.querySelector('.inst-img-upload-input');

        const imgPickPhase  = div.querySelector('.inst-img-pick-phase');
        const imgLabelPhase = div.querySelector('.inst-img-label-phase');
        const imgLabelInput = div.querySelector('.inst-img-label-input');
        const imgConfirmBtn = div.querySelector('.inst-img-confirm-btn');
        const imgBackBtn    = div.querySelector('.inst-img-back-btn');
        const imgSelectedName = div.querySelector('.inst-img-selected-name');
        let _pendingFilename = null;

        function showPickPhase() {
            imgPickPhase.classList.remove('hidden');
            imgLabelPhase.classList.add('hidden');
            _pendingFilename = null;
        }
        function showLabelPhase(filename) {
            _pendingFilename = filename;
            imgSelectedName.textContent = filename;
            imgLabelInput.value = '';
            imgPickPhase.classList.add('hidden');
            imgLabelPhase.classList.remove('hidden');
            imgLabelInput.focus();
        }
        function insertImageTag(filename, label) {
            const tag = label ? `[[IMAGE:${filename}|${label}]]` : `[[IMAGE:${filename}]]`;
            const pos = textInput.selectionStart || textInput.value.length;
            textInput.value = textInput.value.slice(0, pos) + tag + textInput.value.slice(pos);
            textInput.focus();
            imgDropdown.classList.add('hidden');
            showPickPhase();
            markAsChanged();
        }

        imgBackBtn.addEventListener('click', () => showPickPhase());
        imgConfirmBtn.addEventListener('click', () => {
            if (_pendingFilename) insertImageTag(_pendingFilename, imgLabelInput.value.trim());
        });
        imgLabelInput.addEventListener('keydown', e => { if (e.key === 'Enter') imgConfirmBtn.click(); });

        async function populateImageList() {
            imgList.innerHTML = '<li class="px-3 py-2 text-slate-400 italic">Lade…</li>';
            try {
                const res = await fetch('/api/template_images');
                const files = await res.json();
                if (!files.length) {
                    imgList.innerHTML = '<li class="px-3 py-2 text-slate-400 italic">Noch keine Bilder vorhanden</li>';
                } else {
                    imgList.innerHTML = files.map(f =>
                        `<li class="px-3 py-1.5 hover:bg-blue-50 flex items-center gap-2 border-b border-slate-50 last:border-0" data-filename="${f}">
                            <span class="material-icons-outlined text-[12px] text-blue-400 flex-shrink-0">image</span>
                            <span class="truncate flex-1 cursor-pointer img-pick-name">${f}</span>
                            <button class="img-delete-btn flex-shrink-0 text-slate-300 hover:text-red-500 p-0.5" title="Bild löschen" data-filename="${f}">
                                <span class="material-icons-outlined text-[14px]">delete_outline</span>
                            </button>
                        </li>`
                    ).join('');
                    imgList.querySelectorAll('.img-pick-name').forEach(span => {
                        span.addEventListener('click', () => showLabelPhase(span.closest('li').dataset.filename));
                    });
                    imgList.querySelectorAll('.img-delete-btn').forEach(btn => {
                        btn.addEventListener('click', async (e) => {
                            e.stopPropagation();
                            const fname = btn.dataset.filename;
                            if (!confirm(`Bild "${fname}" wirklich löschen?`)) return;
                            try {
                                const res = await fetch(`/api/template_images/${encodeURIComponent(fname)}`, { method: 'DELETE' });
                                if (!res.ok) { const err = await res.json(); alert(err.error || 'Löschen fehlgeschlagen'); return; }
                                await populateImageList();
                            } catch { alert('Netzwerkfehler beim Löschen'); }
                        });
                    });
                }
            } catch { imgList.innerHTML = '<li class="px-3 py-2 text-red-400">Fehler beim Laden</li>'; }
        }

        imgInsertBtn.addEventListener('click', e => {
            e.stopPropagation();
            refInsertDropdown.classList.add('hidden');
            toolRefDropdown.classList.add('hidden');
            const opening = imgDropdown.classList.toggle('hidden');
            if (!imgDropdown.classList.contains('hidden')) { showPickPhase(); populateImageList(); }
        });
        imgUploadInput.addEventListener('change', async () => {
            const file = imgUploadInput.files[0];
            if (!file) return;
            const fd = new FormData();
            fd.append('file', file);
            try {
                const res = await fetch('/api/template_images/upload', { method: 'POST', body: fd });
                if (!res.ok) { const err = await res.json(); alert(err.error || 'Upload fehlgeschlagen'); return; }
                const data = await res.json();
                await populateImageList();
                showLabelPhase(data.filename);
            } catch { alert('Netzwerkfehler beim Upload'); }
            imgUploadInput.value = '';
        });
        document.addEventListener('click', e => {
            if (!imgDropdown.contains(e.target) && e.target !== imgInsertBtn) imgDropdown.classList.add('hidden');
        }, {capture: true});

        const chipsContainer = div.querySelector('.inst-hw-chips');
        renderInstChips(chipsContainer, optObjs, () => markAsChanged());

        const freeInput = div.querySelector('.inst-free-text-input');
        freeInput.addEventListener('keydown', e => {
            if (e.key === 'Enter') {
                e.preventDefault();
                const val = freeInput.value.trim();
                if (!val) return;
                const existing = [...chipsContainer.querySelectorAll('[data-label]')].map(c => c.dataset.label);
                if (!existing.includes(val)) {
                    const cur = [...chipsContainer.querySelectorAll('[data-label]')].map(c => c.dataset.hwUuid ? {label:c.dataset.label, hw_uuid:c.dataset.hwUuid} : {label:c.dataset.label});
                    renderInstChips(chipsContainer, [...cur, {label: val}], () => markAsChanged());
                }
                freeInput.value = '';
                markAsChanged();
            }
        });

        const hwSearchBtn = div.querySelector('.inst-hw-search-btn');
        const hwDropdown = div.querySelector('.inst-hw-dropdown');
        const hwSearchInput = div.querySelector('.inst-hw-search-input');
        const hwResults = div.querySelector('.inst-hw-results');

        hwSearchBtn.addEventListener('click', async e => {
            e.stopPropagation();
            hwDropdown.classList.toggle('hidden');
            if (!hwDropdown.classList.contains('hidden')) {
                const items = await fetchHwInventory();
                renderHwResults(items, '');
                hwSearchInput.focus();
            }
        });

        function renderHwResults(items, query) {
            const filtered = query ? items.filter(i => i.label.toLowerCase().includes(query.toLowerCase())) : items;
            hwResults.innerHTML = filtered.slice(0, 50).map(i =>
                `<li class="px-3 py-1.5 hover:bg-blue-50 cursor-pointer flex items-center gap-1.5 border-b border-slate-50 last:border-0" data-label="${i.label}" data-hw-uuid="${i.hw_uuid}">
                    <span class="material-icons-outlined text-[11px] text-blue-400">inventory_2</span>${i.label}
                </li>`
            ).join('') || '<li class="px-3 py-2 text-slate-400">Keine Treffer</li>';
            hwResults.querySelectorAll('li[data-hw-uuid]').forEach(li => {
                li.addEventListener('click', () => {
                    const opt = {label: li.dataset.label, hw_uuid: li.dataset.hwUuid};
                    const existing = [...chipsContainer.querySelectorAll('[data-label]')].map(c => c.dataset.hwUuid || c.dataset.label);
                    if (!existing.includes(opt.hw_uuid)) {
                        const cur = [...chipsContainer.querySelectorAll('[data-label]')].map(c => c.dataset.hwUuid ? {label:c.dataset.label, hw_uuid:c.dataset.hwUuid} : {label:c.dataset.label});
                        renderInstChips(chipsContainer, [...cur, opt], () => markAsChanged());
                        markAsChanged();
                    }
                    hwDropdown.classList.add('hidden');
                });
            });
        }

        hwSearchInput.addEventListener('input', async () => {
            const items = await fetchHwInventory();
            renderHwResults(items, hwSearchInput.value);
        });

        document.addEventListener('click', e => {
            if (!hwDropdown.contains(e.target) && e.target !== hwSearchBtn) hwDropdown.classList.add('hidden');
        }, {capture: true});

        div.querySelector('.inst-type-select').addEventListener('change', function() {
            div.querySelector('.inst-options-wrapper').classList.toggle('hidden', this.value !== 'part_selection');
            div.querySelector('.inst-text-input-wrapper').classList.toggle('hidden', this.value !== 'text_input');
            markAsChanged();
        });
        div.querySelector('.inst-text-input').addEventListener('input', () => markAsChanged());
        div.querySelector('.inst-placeholder-input').addEventListener('input', () => markAsChanged());

        return div;
    }

    function createReferenceElement(ref) {
        ref = ref || {};
        const div = document.createElement('div');
        div.className = 'reference-item flex items-center gap-1.5';
        div.innerHTML = `
            <input type="text" list="ref-doc-types" class="ref-type-select form-input text-xs py-0.5 flex-shrink-0 font-mono font-bold uppercase" style="width:80px;" placeholder="AMM…" value="${ref.doc_type||''}">
            <datalist id="ref-doc-types">
                <option value="AMM"><option value="IPC"><option value="SB"><option value="ESM">
                <option value="CMM"><option value="EO"><option value="QEC"><option value="AD">
                <option value="SRM"><option value="TM"><option value="WI"><option value="STD">
            </datalist>
            <input type="text" class="ref-number-input form-input text-xs py-0.5 flex-shrink-0" style="width:110px;" placeholder="72-10-01" value="${ref.doc_number||''}">
            <input type="text" class="ref-chapter-input form-input text-xs py-0.5 flex-shrink-0" style="width:90px;" placeholder="Task/Fig/Tbl" value="${ref.chapter||''}">
            <input type="text" class="ref-title-input form-input text-xs py-0.5 flex-1" placeholder="Title / Beschreibung" value="${ref.title||''}">
            <input type="url"  class="ref-url-input form-input text-xs py-0.5 flex-shrink-0" style="width:110px;" placeholder="https://... (opt.)" value="${ref.url||''}">
            <button type="button" class="delete-reference-btn flex-shrink-0 text-slate-300 hover:text-red-500 p-0.5" title="Löschen">
                <span class="material-icons-outlined" style="font-size:16px;">close</span>
            </button>
        `;
        div.querySelector('.delete-reference-btn').addEventListener('click', () => { div.remove(); updateRefsCount(div.closest('.references-section')); markAsChanged(); });
        div.querySelectorAll('input, select').forEach(el => el.addEventListener('input', () => markAsChanged()));
        return div;
    }

    function updateRefsCount(section) {
        if (!section) return;
        const count = section.querySelectorAll('.reference-item').length;
        const label = section.querySelector('.refs-count-label');
        if (label) label.textContent = `${count} Applicable Document${count !== 1 ? 's' : ''}`;
        const ds = section.closest('.details-section');
        if (ds) updateDetailsSummary(ds);
    }

    function initializeSortable(element, groupName) {
        Sortable.create(element, {
            group: groupName,
            animation: 150,
            handle: '.drag-handle',
            ghostClass: 'sortable-ghost',
            onEnd: () => markAsChanged()
        });
    }


    function markAsChanged() {
        hasChanges = true;
        updateSaveButtonState();
    }

    function updateSaveButtonState() {
        dom.saveAllChangesBtn.disabled = !hasChanges;
    }

    function readDataFromDOM() {
        const steps = [];
        let mainStepCounter = 1;
        document.querySelectorAll('.main-step-container').forEach(container => {
            const mainStepWrapper = container.querySelector('.main-step');
            const mainStepItem = mainStepWrapper.querySelector('.step-list-item');
            const mainStepId = String(mainStepCounter++);
            steps.push({
                id: mainStepId,
                parent_id: null,
                name: mainStepItem.querySelector('.step-name-input').value.trim(),
                document_template: mainStepItem.querySelector('.document-template-select').value || null
            });
            const sublist = container.querySelector('.sub-step-list');
            if (sublist) {
                Array.from(sublist.children).forEach((subLi, index) => {
                    const subStepItem = subLi.querySelector('.step-list-item');
                    const subStepId = `${mainStepId}.${index + 1}`;

                    // Tools der 3. Ebene lesen
                    const tools = [];
                    const toolsContainer = subLi.querySelector('.tool-items-container');
                    if (toolsContainer) {
                        toolsContainer.querySelectorAll('.tool-item').forEach(toolEl => {
                            const toolType = toolEl.querySelector('.tool-type-select').value;
                            const labelInput = toolEl.querySelector('.tool-label-input');
                            const toolLabel = labelInput ? labelInput.value.trim() : '';
                            const toolHwUuid = labelInput ? (labelInput.dataset.hwUuid || null) : null;
                            const toolQty = (toolEl.querySelector('.tool-quantity-input').value || '').trim() || null;
                            if (toolLabel) tools.push({type: toolType, label: toolLabel, hw_uuid: toolHwUuid || null, quantity: toolQty});
                        });
                    }

                    // Instructions der 3. Ebene lesen
                    const instructions = [];
                    const itemsContainer = subLi.querySelector('.instruction-items-container');
                    if (itemsContainer) {
                        itemsContainer.querySelectorAll('.instruction-item').forEach((instEl, instIdx) => {
                            const type = instEl.querySelector('.inst-type-select').value;
                            const text = instEl.querySelector('.inst-text-input').value.trim();
                            if (!text && type === 'normal') return; // Leere Normal-Items überspringen
                            const instItem = { step_num: `${subStepId}.${instIdx + 1}`, type, text };
                            if (type === 'part_selection') {
                                const chips = instEl.querySelectorAll('.inst-hw-chips [data-label]');
                                instItem.options = [...chips].map(c => c.dataset.hwUuid ? {label: c.dataset.label, hw_uuid: c.dataset.hwUuid} : {label: c.dataset.label});
                            }
                            if (type === 'text_input') {
                                const ph = instEl.querySelector('.inst-placeholder-input');
                                if (ph && ph.value.trim()) instItem.placeholder = ph.value.trim();
                            }
                            instructions.push(instItem);
                        });
                    }

                    // References (Applicable Documents) der 3. Ebene lesen
                    const references = [];
                    const refsContainer = subLi.querySelector('.reference-items-container');
                    if (refsContainer) {
                        refsContainer.querySelectorAll('.reference-item').forEach(refEl => {
                            const doc_type   = refEl.querySelector('.ref-type-select').value;
                            const doc_number = refEl.querySelector('.ref-number-input').value.trim();
                            const chapter    = refEl.querySelector('.ref-chapter-input').value.trim();
                            const title      = refEl.querySelector('.ref-title-input').value.trim();
                            const url        = refEl.querySelector('.ref-url-input').value.trim();
                            if (doc_number || title) references.push({ doc_type, doc_number, chapter, title, url: url || null });
                        });
                    }

                    steps.push({
                        id: subStepId,
                        parent_id: mainStepId,
                        name: subStepItem.querySelector('.step-name-input').value.trim(),
                        document_template: subStepItem.querySelector('.document-template-select').value || null,
                        tools: tools,
                        instructions: instructions,
                        references: references
                    });
                });
            }
        });

        const timelineEvents = [];
        dom.timelineEventsList.querySelectorAll('.timeline-event-item').forEach(li => {
            const name = li.querySelector('.event-name-input').value.trim();
            const duration = parseInt(li.querySelector('.event-duration-input').value, 10) || 1;
            const color = li.querySelector('.event-color-input').value.trim();
            if (name) {
                timelineEvents.push({
                    name: name,
                    duration_shifts: duration,
                    color: color,
                    assigns: {
                        people: li.querySelector('.assigns-people-input').checked,
                        rooms: li.querySelector('.assigns-rooms-input').checked,
                        hardware: li.querySelector('.assigns-hardware-input').checked,
                    },
                    releases: {
                        hardware: li.querySelector('.releases-hardware-input').checked,
                    }
                });
            }
        });

        const laufkarten = [];
        dom.laufkartenList.querySelectorAll('.laufkarte-item').forEach(li => {
            const name = li.querySelector('.lk-name-input').value.trim();
            if (name) laufkarten.push({ id: li.dataset.id, name });
        });

        return {
            [selectedType]: {
                steps: steps,
                timeline_events: timelineEvents,
                laufkarten: laufkarten
            }
        };
    }

    dom.addNewTypeBtn.addEventListener('click', () => {
        const newTypeName = prompt("Name für den neuen Triebwerkstyp (z.B. PW1900G):");
        if (newTypeName && newTypeName.trim()) {
            const trimmedName = newTypeName.trim();
            if (templatesData[trimmedName]) {
                alert("Ein Template mit diesem Namen existiert bereits."); return;
            }
            templatesData[trimmedName] = { steps: [], timeline_events: [], laufkarten: [] };
            selectedType = trimmedName; 
            markAsChanged(); 
            renderTypeList(); 
            renderViews();
        }
    });

    dom.deleteTypeBtn.addEventListener('click', async () => {
            if (selectedType && selectedType !== 'default' && confirm(`Möchten Sie das Template für "${selectedType}" wirklich und endgültig löschen?`)) {
                // 1. Aus lokalem Objekt löschen
                delete templatesData[selectedType]; 
                
                // 2. Sofort an den Server senden
                try {
                    const response = await fetch("{{ url_for('api_save_process_templates') }}", {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify(templatesData) // Sende das gesamte, bereinigte Objekt
                    });
    
                    if (!response.ok) {
                        const error = await response.json(); 
                        throw new Error(error.error || 'Fehler beim Löschen auf dem Server.');
                    }
    
                    // 3. Erfolg: Zustand zurücksetzen
                    hasChanges = false;
                    selectedType = null;
                    updateSaveButtonState();
                    
                    alert("Template erfolgreich gelöscht.");
                    
                    // 4. UI neu laden
                    renderTypeList();
                    renderViews();
    
                } catch (error) {
                    console.error(error); 
                    alert("Fehler: " + error.message);
                    // Bei Fehler: Daten neu laden, um inkonsistenten Zustand zu vermeiden
                    fetchData();
                }
            }
        });

    dom.addNewMainStepBtn.addEventListener('click', () => {
        if (selectedType) {
            const newStep = { id: `new_${Date.now()}`, parent_id: null, name: 'Neuer Hauptschritt', document_template: null, children: [] };
            const mainStepContainer = document.createElement('li');
            mainStepContainer.className = 'main-step-container';
            mainStepContainer.appendChild(createStepElement(newStep));
            dom.stepsList.appendChild(mainStepContainer);         
            markAsChanged();
        }
    });

    dom.addNewTimelineEventBtn.addEventListener('click', () => {
        const newEvent = { name: 'Neues Event', duration_shifts: 1, color: '#9CA3AF', assigns: { people: true, rooms: false, hardware: false }, releases: { hardware: false } };
        dom.timelineEventsList.appendChild(createTimelineEventElement(newEvent));
        attachTimelineEventListeners();
        markAsChanged();
    });

    dom.saveAllChangesBtn.addEventListener('click', async () => {
        const saves = [];

        if (currentView === 'manuals') {
            saves.push(saveManualCatalogToServer());
        } else if (selectedType) {
            const dataToSave = readDataFromDOM();
            const fullDataToSend = { ...templatesData, ...dataToSave };
            saves.push(
                fetch("{{ url_for('api_save_process_templates') }}", {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(fullDataToSend)
                }).then(async r => {
                    if (!r.ok) throw new Error((await r.json()).error || 'Fehler beim Speichern.');
                })
            );
        }

        try {
            await Promise.all(saves);
            hasChanges = false;
            updateSaveButtonState();
            alert("Änderungen erfolgreich gespeichert!");
            if (currentView !== 'manuals') fetchData();
        } catch (error) {
            console.error(error); alert("Fehler: " + error.message);
        }
    });

    fetchData();
});
</script>
</body>
</html>
"""

FILE_MANAGER_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Dateiverwaltung</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <!-- NEU: Material Symbols für die Icons aus dem Design-Beispiel -->
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
        .action-btn { @apply inline-flex items-center justify-center w-9 h-9 rounded-full text-slate-500 hover:bg-slate-200 hover:text-slate-700 transition-colors; }
        .action-btn.delete:hover { @apply bg-red-100 text-red-600; }

        /* Styles für die einklappbare Sidebar */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }

        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator {
            display: none;
        }
        #sidebar.is-collapsed .sidebar-separator {
            display: block;
            height: 1px;
            background-color: #e2e8f0;
            margin: 0.5rem 0.75rem;
        }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <!-- Hauptinhalt -->
    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Dateiverwaltung</h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">
            <div class="max-w-7xl mx-auto">
                <header class="flex flex-wrap items-center justify-between gap-4 mb-6">
                    <div class="flex flex-col gap-1">
                        <h2 class="text-2xl font-bold text-slate-800">Stammverzeichnis (db)</h2>
                        <p class="text-sm font-normal text-slate-500">Laden, verwalten und ersetzen Sie Ihre Konfigurationsdateien.</p>
                    </div>
                    <div class="flex items-center gap-2"> <!-- NEUER WRAPPER -->
                        <!-- Button zum Downloaden des gesamten Ordners -->
                        <a href="{{ url_for('download_db_folder', folderpath='ROOT') }}" class="flex min-w-[84px] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg h-11 px-5 bg-slate-100 text-slate-700 text-sm font-medium leading-normal tracking-[0.015em] ring-1 ring-slate-200 hover:bg-slate-200">
                            <span class="material-symbols-outlined">archive</span>
                            <span class="truncate">Alles zippen</span>
                        </a>
                        <!-- Bestehender Upload-Button -->
                        <form method="POST" action="{{ url_for('upload_new_db_file', dirpath='.') }}" enctype="multipart/form-data">
                            <label class="flex min-w-[84px] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg h-11 px-5 bg-blue-600 text-white text-sm font-bold leading-normal tracking-[0.015em] hover:bg-blue-700">
                                <span class="material-symbols-outlined">upload_file</span>
                                <span class="truncate">Datei hochladen</span>
                                <input type="file" name="new_file" class="hidden" onchange="this.form.submit()">
                            </label>
                        </form>
                    </div>
                </header>

                {% with messages = get_flashed_messages(with_categories=true) %}
                  {% if messages %}
                    <div class="mb-4 space-y-2">
                    {% for category, message in messages %}
                      <div class="p-4 rounded-md text-sm {% if category == 'success' %}bg-green-100 text-green-800{% elif category == 'error' %}bg-red-100 text-red-800{% else %}bg-blue-100 text-blue-800{% endif %}" role="alert">
                        <p class="font-medium">{{ message }}</p>
                      </div>
                    {% endfor %}
                    </div>
                  {% endif %}
                {% endwith %}

                <div class="overflow-hidden bg-white rounded-xl border border-slate-200 shadow-sm">
                    <div class="overflow-x-auto">
                        <table class="w-full text-sm text-left">
                            <thead class="bg-slate-50 text-xs text-slate-500 uppercase tracking-wider">
                                <tr>
                                    <th class="px-6 py-3" scope="col">Dateiname</th>
                                    <th class="px-6 py-3 text-right" scope="col">Aktionen</th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-slate-200">
                                {% for item in file_tree %}
                                    <tr class="hover:bg-slate-50">
                                        <td class="px-6 py-4 font-medium text-slate-800 whitespace-nowrap">
                                            <div class="flex items-center gap-3" style="padding-left: {{ item.depth * 24 }}px;">
                                                {% if item.type == 'dir' %}
                                                    <span class="material-symbols-outlined text-amber-500">folder</span>
                                                {% else %}
                                                    <span class="material-symbols-outlined text-slate-400">description</span>
                                                {% endif %}
                                                <span>{{ item.name }}</span>
                                            </div>
                                        </td>
                                        <td class="px-6 py-4 text-right">
                                            <div class="flex items-center justify-end space-x-1">
                                                {% if item.type == 'dir' %}
                                                    <!-- NEU: Download-Button für Ordner -->
                                                    <a href="{{ url_for('download_db_folder', folderpath=item.path) }}" class="action-btn" title="Diesen Ordner als ZIP herunterladen">
                                                        <span class="material-symbols-outlined text-lg">folder_zip</span>
                                                    </a>
                                                    <form method="POST" action="{{ url_for('upload_new_db_file', dirpath=item.path) }}" enctype="multipart/form-data">
                                                        <label class="action-btn cursor-pointer" title="Neue Datei in diesen Ordner hochladen">
                                                            <span class="material-symbols-outlined text-lg">create_new_folder</span>
                                                            <input type="file" name="new_file" class="hidden" onchange="this.form.submit()">
                                                        </label>
                                                    </form>
                                                {% else %}
                                                    <a href="{{ url_for('download_db_file', filepath=item.path) }}" class="action-btn" title="Datei herunterladen">
                                                        <span class="material-symbols-outlined text-lg">download</span>
                                                    </a>
                                                    <form method="POST" action="{{ url_for('upload_db_file', filepath=item.path) }}" enctype="multipart/form-data">
                                                        <label class="action-btn cursor-pointer" title="Datei ersetzen">
                                                            <span class="material-symbols-outlined text-lg">upload</span>
                                                            <input type="file" name="file" class="hidden" onchange="this.form.submit()">
                                                        </label>
                                                    </form>
                                                    <form method="POST" action="{{ url_for('delete_db_file', filepath=item.path) }}">
                                                        <button type="button" class="action-btn delete" title="Datei löschen" onclick="confirmDelete(this, '{{ item.name }}')">
                                                            <span class="material-symbols-outlined text-lg">delete</span>
                                                        </button>
                                                    </form>
                                                {% endif %}
                                            </div>
                                        </td>
                                    </tr>
                                {% else %}
                                    <tr>
                                        <td colspan="2" class="p-8 text-center text-slate-500">Der Ordner 'db' ist leer oder konnte nicht gelesen werden.</li>
                                    </tr>
                                {% endfor %}
                            </tbody>
                        </table>
                    </div>
                </div>
                <div class="mt-4 text-xs text-gray-500">
                    <p><span class="font-semibold">Hinweis:</span> Änderungen an Konfigurationsdateien (.csv) erfordern in der Regel einen Neustart der Anwendung, damit sie wirksam werden.</p>
                </div>
            </div>
        </div>
    </main>
</div>
<script>
    // --- Logik für die einklappbare Sidebar ---
    document.addEventListener('DOMContentLoaded', () => {
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    toggleBtnIcon.textContent = 'chevron_left';
                }
            };
            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
        }

        // --- Bestehende Logik für die Dateiverwaltung ---
        function confirmDelete(buttonElement, fileName) {
            const message = `Möchten Sie die Datei '${fileName}' wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.`;
            if (confirm(message)) {
                const form = buttonElement.closest('form');
                if (form) {
                  form.submit();
                }
            }
        }

        // Mache die Funktion global verfügbar, damit onclick im HTML sie finden kann
        window.confirmDelete = confirmDelete;
    });
</script>

</body>
</html>
"""

EMPLOYEE_DASHBOARD_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>MBET - Mitarbeiter Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .kpi-card .text-gray-600 { color: #4b5563 !important; }
        .kpi-card .text-gray-900 { color: #111827 !important; }
        .text-slate-800 { color: #1e293b !important; }
        .text-gray-700 { color: #374151 !important; }
        .text-sm.text-gray-600 { color: #4b5563 !important; }
        .text-sm.text-gray-500 { color: #6b7280 !important; }
        .form-select, .form-input, .form-checkbox { @apply border-gray-300 focus:border-blue-500 focus:ring-blue-500 rounded-md shadow-sm bg-white text-gray-700; }
        .apexcharts-tooltip { @apply bg-white text-gray-800 border border-gray-200 shadow-lg; }
        .custom-scrollbar::-webkit-scrollbar { width: 6px; }
        .custom-scrollbar::-webkit-scrollbar-track { background: #e5e7eb; border-radius: 10px; }
        .custom-scrollbar::-webkit-scrollbar-thumb { background: #9ca3af; border-radius: 10px; }
        .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #6b7280; }
        .apexcharts-tooltip-custom { background: #fff; color: #333; border: 1px solid #e0e0e0; padding: 8px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.15); }

        /* Styles für die einklappbare Sidebar */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator {
            display: none;
        }
        #sidebar.is-collapsed .sidebar-separator {
            display: block;
            height: 1px;
            background-color: #e2e8f0;
            margin: 0.5rem 0.75rem;
        }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        .sfm-tooltip {
            display: none;
            position: absolute;
            top: 100%;
            left: 0;
            width: 320px;
            background: white;
            border: 1px solid #e2e8f0;
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
            z-index: 100;
            border-radius: 0.75rem;
            padding: 1rem;
            margin-top: 0.5rem;
        }
        .group:hover .sfm-tooltip {
            display: block;
        }
        .tooltip-section-title {
            font-size: 10px;
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: 0.05em;
            color: #94a3b8;
            margin-bottom: 0.5rem;
            border-bottom: 1px solid #f1f5f9;
            padding-bottom: 0.25rem;
        }
        .task-checkbox:checked + span { text-decoration: line-through; color: #94a3b8; }
        .task-delete-btn { display: none; }
        .task-item:hover .task-delete-btn { display: block; }
        /* Drag & Drop Styles */
        .sortable-ghost { opacity: 0.4; background-color: #f1f5f9; border: 1px dashed #cbd5e1; }
        .sortable-drag { cursor: grabbing; }
        .task-item { cursor: default; }
        /* Griff-Bereich für Drag (optional, hier machen wir das ganze Element ziehbar außer Input) */
        .drag-handle { cursor: grab; color: #cbd5e1; margin-right: 4px; }
        .drag-handle:active { cursor: grabbing; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

<main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Shop Floor Management</h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8"> <!-- WICHTIG: Padding-Wrapper öffnen -->
            
            <!-- NAV TABS -->
            <div class="mb-6 border-b border-slate-200">
                <nav class="-mb-px flex space-x-8">
                    <button onclick="switchEmpTab('sfm', this)" class="emp-tab-btn active border-b-2 border-blue-500 text-blue-600 px-1 pb-4 text-sm font-medium">SFM Board</button>
                    <button onclick="switchEmpTab('projektplan', this)" class="emp-tab-btn border-b-2 border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300 px-1 pb-4 text-sm font-medium">Projektplan</button>
                </nav>
            </div>
            <!-- TAB 0: SFM BOARD -->
            <div id="view-sfm" class="emp-tab-content">
                
                <!-- Reihe 1: KPIs (Dashboard Design) -->
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
                    
            <!-- SAFETY / CRITICAL KACHEL -->
            <div class="group bg-white rounded-lg border border-slate-200 shadow-sm p-6 relative flex flex-col justify-center cursor-help">
                <div class="absolute left-0 top-0 bottom-0 w-1 {{ 'bg-red-500' if sfm_stats.critical_total > 0 else 'bg-green-500' }}"></div>
                
                <div class="flex items-center gap-4 mb-3">
                    <div class="h-10 w-10 rounded-full flex items-center justify-center flex-shrink-0 {{ 'bg-red-50 text-red-600' if sfm_stats.critical_total > 0 else 'bg-green-50 text-green-600' }}">
                        <span class="material-icons-outlined text-xl">health_and_safety</span>
                    </div>
                    <h2 class="text-lg font-bold text-slate-800">Safety & Critical</h2>
                </div>
            
                <div class="pl-[3.5rem]">
                    <div class="flex items-baseline gap-2">
                        <span class="text-3xl font-bold text-slate-800">{{ sfm_stats.critical_total }}</span>
                        <span class="text-sm font-medium text-slate-500 uppercase tracking-wider">Kritische Themen</span>
                    </div>
                </div>
            
                <!-- TOOLTIP SAFETY -->
                <div class="sfm-tooltip">
                    {% if sfm_stats.details.critical_prio %}
                        <div class="tooltip-section-title text-red-500">Prio: Critical</div>
                        <ul class="text-xs text-slate-600 space-y-1 mb-4">
                            {% for item in sfm_stats.details.critical_prio %}<li>• {{ item }}</li>{% endfor %}
                        </ul>
                    {% endif %}
                    
                    {% if sfm_stats.details.blocked_units %}
                        <div class="tooltip-section-title text-red-600">Anlagen-Sperren</div>
                        <ul class="text-xs text-slate-600 space-y-1 mb-4">
                            {% for item in sfm_stats.details.blocked_units %}<li>• {{ item }}</li>{% endfor %}
                        </ul>
                    {% endif %}
            
                    {% if sfm_stats.details.blocked_hw %}
                        <div class="tooltip-section-title text-orange-600">Hardware-Sperren</div>
                        <ul class="text-xs text-slate-600 space-y-1">
                            {% for item in sfm_stats.details.blocked_hw %}<li>• {{ item }}</li>{% endfor %}
                        </ul>
                    {% endif %}
            
                    {% if not sfm_stats.details.critical_prio and not sfm_stats.details.blocked_units and not sfm_stats.details.blocked_hw %}
                        <p class="text-xs text-green-600 italic">Keine kritischen Blockaden vorhanden.</p>
                    {% endif %}
                </div>
            </div>
            
            <!-- QUALITY / OPEN KACHEL -->
            <div class="group bg-white rounded-lg border border-slate-200 shadow-sm p-6 relative flex flex-col justify-center cursor-help">
                <div class="absolute left-0 top-0 bottom-0 w-1 bg-amber-500"></div>
                
                <div class="flex items-center gap-4 mb-3">
                    <div class="h-10 w-10 rounded-full flex items-center justify-center flex-shrink-0 bg-amber-50 text-amber-600">
                        <span class="material-icons-outlined text-xl">assignment_late</span>
                    </div>
                    <h2 class="text-lg font-bold text-slate-800">Quality & Open</h2>
                </div>
            
                <div class="pl-[3.5rem]">
                    <div class="flex items-baseline gap-2">
                        <span class="text-3xl font-bold text-slate-800">{{ sfm_stats.open_issues }}</span>
                        <span class="text-sm font-medium text-slate-500 uppercase tracking-wider">Offene Issues</span>
                    </div>
                </div>
            
                <!-- TOOLTIP QUALITY -->
                <div class="sfm-tooltip">
                    <div class="tooltip-section-title">Alle offenen Issues</div>
                    <div class="max-h-60 overflow-y-auto pr-2 custom-scrollbar">
                        <ul class="text-xs text-slate-600 space-y-2">
                            {% for item in sfm_stats.details.critical_prio + sfm_stats.details.blocked_units + sfm_stats.details.blocked_hw + sfm_stats.details.other_open %}
                                <li class="border-b border-slate-50 pb-1">• {{ item }}</li>
                            {% else %}
                                <li class="italic text-slate-400">Keine offenen Issues.</li>
                            {% endfor %}
                        </ul>
                    </div>
                </div>
            </div>
                
                <!-- PEOPLE (Heute) -->
                    <div class="bg-white rounded-lg border border-slate-200 shadow-sm p-6 relative overflow-hidden flex flex-col justify-center">
                        <div class="absolute left-0 top-0 bottom-0 w-1 bg-blue-500"></div>
                        
                        <div class="flex items-center gap-4 mb-3">
                            <div class="h-10 w-10 rounded-full flex items-center justify-center flex-shrink-0 bg-blue-50 text-blue-600">
                                <span class="material-icons-outlined text-xl">groups</span>
                            </div>
                            <h2 class="text-lg font-bold text-slate-800">People (Heute)</h2>
                            <button onclick="openPeopleGroupsModal()" title="Gruppen konfigurieren" class="ml-auto text-slate-400 hover:text-blue-500 transition-colors">
                                <span class="material-icons-outlined text-lg">settings</span>
                            </button>
                        </div>
                
                        <div class="pl-[3.5rem]">
                            <div class="flex items-baseline gap-3">
                                <span class="text-3xl font-bold text-slate-800" id="sfm-people-count">-</span>
                                <span class="text-xl font-medium text-slate-400" id="sfm-people-total" style="display:none">/ <span id="sfm-people-total-val">-</span></span>

                                <!-- NEU: Differenz Anzeige -->
                                <div id="sfm-people-diff" class="flex items-center text-xs font-bold px-2 py-0.5 rounded-full hidden">
                                    <!-- Icon und Wert werden per JS gesetzt -->
                                </div>
                            </div>
                            <p class="text-sm font-medium text-slate-500 uppercase tracking-wider mt-1">Mitarbeiter anwesend</p>
                        </div>
                    </div>
                </div>
                <div class="mb-6 flex items-center gap-2 text-slate-400 border-b border-slate-100 pb-2 ml-1">
                    <span class="material-icons-outlined text-sm">calendar_today</span>
                    <span id="sfm-current-date-title" class="text-xs font-bold uppercase tracking-widest"></span>
                </div>
            <!-- ZEILE 2: KALENDER & INFOS -->
                <div class="grid grid-cols-1 lg:grid-cols-4 gap-6 h-[500px] mb-6">
                    
                    <!-- LINKER BEREICH: KALENDER (2 Spalten breit) -->
                    <div class="lg:col-span-3 bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
                        <div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50 flex-shrink-0">
                            <h3 class="font-bold text-slate-800 flex items-center gap-2">
                                <span class="material-icons-outlined text-slate-400">calendar_view_week</span> 2-Wochen Vorschau
                            </h3>
                            <span id="autoSaveIndicator" class="text-xs font-medium text-green-600 opacity-0 transition-opacity duration-500 flex items-center gap-1">
                                <span class="material-icons-outlined text-sm">cloud_done</span> Gespeichert
                            </span>
                        </div>
                        <div class="overflow-x-auto flex-grow custom-scrollbar">
                            <table class="w-full text-sm border-collapse h-full" id="sfmCalendarTable">
                                <thead class="bg-slate-50 text-slate-500 uppercase text-[10px] font-bold tracking-wider sticky top-0 z-20">
                                    <tr id="sfmCalendarHeader"></tr>
                                </thead>
                                <tbody id="sfmCalendarBody" class="divide-y divide-slate-200"></tbody>
                            </table>
                        </div>
                    </div>
            
                    <!-- RECHTER BEREICH: AKTUELLE INFOS (1 Spalte breit) -->
                    <div class="lg:col-span-1 bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
                        <div class="px-4 py-4 border-b border-slate-100 bg-blue-50/30 flex-shrink-0">
                            <h3 class="font-bold text-blue-900 flex items-center gap-2 text-sm">
                                <span class="material-icons-outlined text-blue-500">info</span> Aktuelle Infos / Schicht
                            </h3>
                        </div>
                        <div class="flex-grow overflow-y-auto custom-scrollbar p-2 bg-white" id="taskList-info">
                            <!-- JS gefüllt -->
                        </div>
                        <div class="p-3 border-t border-slate-100 bg-slate-50">
                            <input type="text" class="w-full text-sm border border-slate-200 rounded-lg p-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-slate-400" 
                                placeholder="+ Info..." onkeydown="if(event.key === 'Enter') { addTask('info', this.value); this.value = ''; }">
                        </div>
                    </div>
                </div>
            
                <!-- ZEILE 3: THEMEN GRIDS -->
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6 h-96">
                    
                    <!-- 1. QUALITY & SAFETY -->
                    <div class="bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
                        <div class="px-4 py-3 border-b border-slate-100 bg-slate-50/50 flex-shrink-0">
                            <h3 class="font-bold text-slate-800 flex items-center gap-2 text-sm">
                                <span class="material-icons-outlined text-amber-500">health_and_safety</span> Quality & Arbeitssicherheit
                            </h3>
                        </div>
                        <div class="flex-grow overflow-y-auto custom-scrollbar p-2" id="taskList-quality_safety"></div>
                        <div class="p-3 border-t border-slate-100 bg-white">
                            <input type="text" class="w-full text-sm border border-slate-200 rounded-lg p-2 focus:ring-2 focus:ring-amber-500 focus:border-transparent placeholder-slate-400" 
                                placeholder="+ Thema..." onkeydown="if(event.key === 'Enter') { addTask('quality_safety', this.value); this.value = ''; }">
                        </div>
                    </div>
            
                    <!-- 2. ERFOLGE & ESKALATIONEN (Spezial) -->
                    <div class="bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
                        <div class="px-4 py-3 border-b border-slate-100 bg-slate-50/50 flex-shrink-0">
                            <h3 class="font-bold text-slate-800 flex items-center gap-2 text-sm">
                                <span class="material-icons-outlined text-purple-500">campaign</span> Erfolge & Eskalationen
                            </h3>
                        </div>
                        <div class="flex-grow overflow-y-auto custom-scrollbar p-2" id="taskList-success_escalation"></div>
                        
                        <!-- Spezial Input Bereich -->
                        <div class="p-3 border-t border-slate-100 bg-white">
                            <div class="flex gap-2">
                                <input type="text" id="input-success_escalation" class="flex-grow text-sm border border-slate-200 rounded-lg p-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder-slate-400" 
                                    placeholder="Eintrag verfassen...">
                            </div>
                            <div class="flex gap-2 mt-2">
                                <button onclick="addSpecialTask('success')" class="flex-1 bg-green-50 text-green-700 border border-green-200 hover:bg-green-100 text-xs font-bold py-1.5 px-2 rounded flex items-center justify-center gap-1 transition-colors">
                                    <span class="material-icons-outlined text-sm">emoji_events</span> Erfolg
                                </button>
                                <button onclick="addSpecialTask('escalation')" class="flex-1 bg-red-50 text-red-700 border border-red-200 hover:bg-red-100 text-xs font-bold py-1.5 px-2 rounded flex items-center justify-center gap-1 transition-colors">
                                    <span class="material-icons-outlined text-sm">warning</span> Eskalation
                                </button>
                            </div>
                        </div>
                    </div>
            
                    <!-- 3. BACKLOG / ALLGEMEIN -->
                    <div class="bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
                        <div class="px-4 py-3 border-b border-slate-100 bg-slate-50/50 flex-shrink-0">
                            <h3 class="font-bold text-slate-800 flex items-center gap-2 text-sm">
                                <span class="material-icons-outlined text-slate-400">format_list_bulleted</span> Backlog / Allgemein
                            </h3>
                        </div>
                        <div class="flex-grow overflow-y-auto custom-scrollbar p-2" id="taskList-general"></div>
                        <div class="p-3 border-t border-slate-100 bg-white space-y-2">
                            <input type="text" id="backlog-new-text" class="w-full text-sm border border-slate-200 rounded-lg p-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-slate-400"
                                placeholder="+ To-Do..." onkeydown="if(event.key === 'Enter') addBacklogTask()">
                            <div class="flex gap-2">
                                <input type="date" id="backlog-new-due" class="flex-1 text-xs border border-slate-200 rounded-lg px-2 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-slate-600" title="Zieldatum">
                                <select id="backlog-new-assignee" class="flex-1 text-xs border border-slate-200 rounded-lg px-2 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-slate-600 bg-white">
                                    <option value="">— Mitarbeiter —</option>
                                    {% for emp in backlog_employees %}
                                    <option value="{{ emp }}">{{ emp }}</option>
                                    {% endfor %}
                                </select>
                                <button onclick="addBacklogTask()" class="flex-shrink-0 bg-blue-500 hover:bg-blue-600 text-white text-xs font-bold px-3 py-1.5 rounded-lg transition-colors">
                                    <span class="material-icons-outlined text-sm leading-none">add</span>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- ZEILE 4: EFFIZIENZ DIAGRAMM -->
                <div class="mt-6 bg-white rounded-xl shadow-sm border border-slate-200 p-6">
                    <div class="flex justify-between items-center mb-4">
                        <div>
                            <h3 class="font-bold text-slate-800 flex items-center gap-2">
                                <span class="material-icons-outlined text-blue-500">show_chart</span> Gesamtproduktivität
                            </h3>
                            <p class="text-xs text-slate-500">Aktueller Monat (täglich) + ältere Monate als Mittelwert</p>
                        </div>
                        <div class="flex items-center gap-3">
                            <span id="efficiencyTargetBadge" class="text-xs font-bold bg-green-100 text-green-700 px-2 py-1 rounded">Ziel: &gt; 85%</span>
                            <button onclick="toggleEfficiencyEdit()" id="efficiencyEditBtn" class="flex items-center gap-1 text-xs font-semibold text-slate-500 hover:text-blue-600 border border-slate-200 hover:border-blue-300 px-3 py-1.5 rounded-lg transition-colors">
                                <span class="material-icons-outlined text-sm">edit</span> Bearbeiten
                            </button>
                        </div>
                    </div>

                    <!-- Chart Container -->
                    <div id="efficiencyChart" class="w-full h-72"></div>

                    <!-- Edit Panel (versteckt) -->
                    <div id="efficiencyEditPanel" class="hidden mt-4 border-t border-slate-100 pt-4">
                        <div class="flex items-center justify-between mb-3">
                            <!-- Monat-Navigation -->
                            <div class="flex items-center gap-1">
                                <button onclick="efficiencyNavMonth(-1)" class="p-1 rounded hover:bg-slate-100 text-slate-500 hover:text-slate-700" title="Vorheriger Monat">
                                    <span class="material-icons-outlined text-base">chevron_left</span>
                                </button>
                                <span id="efficiencyEditMonthLabel" class="text-xs font-bold text-slate-700 min-w-[90px] text-center uppercase tracking-wider"></span>
                                <button id="efficiencyNavNextBtn" onclick="efficiencyNavMonth(1)" class="p-1 rounded hover:bg-slate-100 text-slate-500 hover:text-slate-700 disabled:opacity-30 disabled:cursor-not-allowed" title="Nächster Monat">
                                    <span class="material-icons-outlined text-base">chevron_right</span>
                                </button>
                            </div>
                            <!-- Zielwert -->
                            <div class="flex items-center gap-2">
                                <label class="text-xs text-slate-500 font-medium">Zielwert:</label>
                                <input type="number" id="efficiencyTargetInput" min="0" max="100" class="w-16 text-xs border border-slate-200 rounded px-2 py-1 text-center focus:ring-1 focus:ring-green-500" onblur="saveEfficiencyTarget()" onkeydown="if(event.key==='Enter') this.blur()">
                                <span class="text-xs text-slate-500">%</span>
                            </div>
                        </div>
                        <div class="overflow-x-auto">
                            <table class="w-full text-xs border-collapse">
                                <thead>
                                    <tr class="bg-slate-50 text-slate-500 uppercase tracking-wider">
                                        <th class="px-3 py-2 text-left font-semibold border border-slate-100">Datum</th>
                                        <th class="px-3 py-2 text-center font-semibold border border-slate-100 text-blue-600">IGT</th>
                                        <th class="px-3 py-2 text-center font-semibold border border-slate-100 text-green-600">CFX</th>
                                        <th class="px-3 py-2 text-center font-semibold border border-slate-100 text-orange-500">PWC</th>
                                        <th class="px-3 py-2 text-center font-semibold border border-slate-100 text-purple-600">Gesamt</th>
                                    </tr>
                                </thead>
                                <tbody id="efficiencyEditTableBody"></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
            <!-- TAB 1: ÜBERSICHT -->
            <!-- TAB 2: MATRIX -->
            <!-- TAB 3: PROJEKTPLAN -->
            <div id="view-projektplan" class="emp-tab-content hidden">
                <div id="projektplan-container">
                    <div class="text-slate-400 italic text-sm p-4 flex items-center gap-2">
                        <span class="material-icons-outlined text-sm animate-spin">refresh</span> Lade Projektplan...
                    </div>
                </div>
            </div>

        </div> <!-- Padding Wrapper Ende -->
    </main>
</div>
<!-- PROJEKTPLAN COMMENTS MODAL (read-only) -->
<div id="ppCommentsOverlay" class="fixed inset-0 bg-gray-800 bg-opacity-60 hidden z-[200] flex items-center justify-center" onclick="if(event.target===this)closePpComments()">
    <div class="bg-white rounded-xl shadow-2xl w-full max-w-lg max-h-[80vh] flex flex-col" onclick="event.stopPropagation()">
        <div class="flex items-center justify-between px-5 py-4 border-b border-slate-200">
            <div>
                <h3 class="text-base font-bold text-slate-800" id="ppCommentsTitle">Kommentare</h3>
                <p class="text-xs text-slate-400 mt-0.5" id="ppCommentsSubtitle"></p>
            </div>
            <button onclick="closePpComments()" class="text-slate-400 hover:text-slate-600 transition-colors">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <div id="ppCommentsList" class="overflow-y-auto flex-1 px-5 py-4 space-y-4">
            <p class="text-slate-400 italic text-sm">Lade Kommentare…</p>
        </div>
    </div>
</div>

<!-- SKILL DETAIL MODAL -->
<div id="skillDetailModal" class="fixed inset-0 bg-gray-900 bg-opacity-50 hidden z-50 flex items-center justify-center" onclick="this.classList.add('hidden')">
    <div class="bg-white rounded-lg shadow-xl p-6 max-w-sm w-full" onclick="event.stopPropagation()">
        <h3 class="text-lg font-bold text-slate-800 mb-1" id="skillDetailTitle">Details</h3>
        <p class="text-sm text-slate-500 mb-4" id="skillDetailSubtitle">Mitarbeiter @ Engine</p>
        
        <div class="space-y-2" id="skillDetailList">
            <!-- Liste der Tasks -->
        </div>
        
        <button class="mt-6 w-full py-2 bg-slate-100 text-slate-600 rounded hover:bg-slate-200 text-sm font-medium" onclick="document.getElementById('skillDetailModal').classList.add('hidden')">Schließen</button>
    </div>
</div>
<script>
    // --- Globale SFM-Variablen (außerhalb DOMContentLoaded für Edit-Panel-Zugriff) ---
    let sfmEfficiencyData = {{ sfm_efficiency_data | default({}) | tojson }} || {};
    let sfmEfficiencyTarget = {{ sfm_efficiency_target | default(85) | tojson }} || 85;
    let efficiencyChartInstance = null;
    // Aktuell im Edit-Panel angezeigter Monat (Jahr + Monat 0-basiert)
    let efficiencyEditYear = new Date().getFullYear();
    let efficiencyEditMonth = new Date().getMonth();

    // --- Logik für die einklappbare Sidebar ---
    document.addEventListener('DOMContentLoaded', function () {
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    toggleBtnIcon.textContent = 'chevron_left';
                }
            };
            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
        }

        const allEmployees = {{ data.full_employee_data | tojson }};
        const allQualifications = {{ data.full_qualification_data | tojson }};
        // SFM People-Kachel: Gruppen-Filter (leer = alle Gruppen zählen)
        let sfmPeopleGroupsFilter = {{ sfm_people_groups_filter | tojson }};

        const globalChartOptions = {
            chart: { fontFamily: 'Inter, sans-serif', toolbar: { show: false }, animations: { speed: 400 } },
            dataLabels: { enabled: false },
            legend: { position: 'bottom', fontSize: '12px', markers: { width: 10, height: 10, radius: 10 }, itemMargin: { horizontal: 10 } }
        };
        const statusLabelsInOrder = ['Abgeschlossen', 'Gestartet', 'Überfällig', 'Geplant'];
        const statusColorsInOrder = ['#10B981', '#F59E0B', '#EF4444', '#6B7280'];

        const trainingStatusChartEl = document.querySelector("#trainingStatusChart");
        const trainingStatusChart = trainingStatusChartEl ? new ApexCharts(trainingStatusChartEl, {
            ...globalChartOptions,
            series: [],
            labels: statusLabelsInOrder,
            colors: statusColorsInOrder,
            chart: { ...globalChartOptions.chart, type: 'donut' },
            plotOptions: { pie: { donut: { labels: { show: true, total: { show: true, label: 'Merkmale' } } } } },
            noData: { text: 'Keine Daten für die aktuelle Filterauswahl.' }
        }) : null;
        if (trainingStatusChart) trainingStatusChart.render();

        const skillDistributionChartEl = document.querySelector("#skillDistributionChart");
        const skillDistributionChart = skillDistributionChartEl ? new ApexCharts(skillDistributionChartEl, {
            ...globalChartOptions, series: [], chart: { ...globalChartOptions.chart, type: 'bar', height: 250, stacked: true },
            plotOptions: { bar: { horizontal: true, borderRadius: 4 } },
            xaxis: { categories: [], labels: { show: false }, axisBorder: { show: false }, axisTicks: { show: false } },
            yaxis: { labels: { show: true, style: { fontSize: '11px' } } },
            grid: { show: false },
            tooltip: { y: { formatter: (val) => val + " Mitarbeiter" } },
            legend: { position: 'top', horizontalAlign: 'left' },
            noData: { text: 'Keine Daten zum Anzeigen.' }
        }) : null;
        if (skillDistributionChart) skillDistributionChart.render();

        const availabilityChartEl = document.querySelector("#availabilityChart");
        const availabilityChart = availabilityChartEl ? new ApexCharts(availabilityChartEl, {
            ...globalChartOptions,
            series: [],
            chart: { ...globalChartOptions.chart, type: 'area', height: 300, zoom: { enabled: false } },
            stroke: { width: 3, curve: 'smooth' },
            xaxis: { type: 'datetime', labels: { format: 'dd MMM' } },
            yaxis: { title: { text: 'Anzahl verfügbarer Mitarbeiter', style: { fontWeight: 500, color: '#6b7280' } } },
            tooltip: { x: { format: 'dd.MM.yyyy' } },
            noData: { text: 'Bitte einen Skill-Typ filtern, um den Trend anzuzeigen.' }
        }) : null;
        if (availabilityChart) availabilityChart.render();

        const employeeFilter = document.getElementById('employeeFilter');
        const groupCheckboxes = document.querySelectorAll('.group-checkbox');
        const skillCheckboxes = document.querySelectorAll('.skill-checkbox');
        const clearFiltersBtn = document.getElementById('clearFiltersBtn');
        const availabilityTimeFilter = document.getElementById('availabilityTimeFilter');
        const employeeDetailCard = document.getElementById('employeeDetailCard');
        const employeeDetailContent = document.getElementById('employeeDetailContent');

        function applyFilters() {
            const selectedEmployeeId = employeeFilter.value;
            const selectedGroups = Array.from(groupCheckboxes).filter(cb => cb.checked).map(cb => cb.value);
            const selectedSkills = Array.from(skillCheckboxes).filter(cb => cb.checked).map(cb => cb.value);

            let filteredQuals = [];

            if (selectedEmployeeId) {
                filteredQuals = allQualifications.filter(q => q.id.toLowerCase() === selectedEmployeeId.toLowerCase());
                groupCheckboxes.forEach(cb => { cb.checked = false; cb.disabled = true; });
                skillCheckboxes.forEach(cb => { cb.checked = false; cb.disabled = true; });
                showEmployeeDetails(selectedEmployeeId);
            } else {
                groupCheckboxes.forEach(cb => cb.disabled = false);
                skillCheckboxes.forEach(cb => cb.disabled = false);
                employeeDetailCard.classList.add('hidden');

                let tempQuals = allQualifications;
                if (selectedGroups.length > 0) {
                    tempQuals = tempQuals.filter(q => selectedGroups.includes(q.quali));
                }
                if (selectedSkills.length > 0) {
                    tempQuals = tempQuals.filter(q => 
                        q.merkmale.some(merkmal => {
                            if (!merkmal.merkmal.includes('/')) return false;
                            const skillName = merkmal.merkmal.split('/')[1];
                            return selectedSkills.includes(skillName) && merkmal.wert;
                        })
                    );
                }
                filteredQuals = tempQuals;
            }

            const relevantMerkmale = new Set();
            filteredQuals.forEach(q => {
                q.merkmale.forEach(m => { if (m.wert) { relevantMerkmale.add(m.merkmal); } });
            });


            const filterContext = {
                isSingleEmployee: !!selectedEmployeeId,
                isGroupOnly: selectedGroups.length > 0 && selectedSkills.length === 0
            };

            updateTrainingStatusChart(filteredQuals);
            updateSkillDistributionChart(filteredQuals, selectedSkills, filterContext);
            updateAvailabilityChart(filteredQuals, selectedSkills);
            updateFilterSummary(filteredQuals);
            updateActiveFilterTags(selectedGroups, selectedSkills);
        }

        function showEmployeeDetails(employeeId) {
            const qualData = allQualifications.find(q => q.id.toLowerCase() === employeeId.toLowerCase());
            if (!qualData) return;

            const abgeschlossen = qualData.merkmale.filter(m => m.wert === 'X').map(m => m.merkmal).sort();
            const inArbeit = qualData.merkmale.filter(m => ['TS', 'TT', 'TP'].includes(m.wert)).map(m => m.merkmal).sort();

            let html = `<h3 class="text-lg font-semibold text-slate-800 mb-4">${qualData.name}</h3>`;
            html += '<h4 class="text-sm font-semibold text-green-600">Abgeschlossen ('+abgeschlossen.length+')</h4>';
            if (abgeschlossen.length > 0) {
                html += '<ul class="list-disc list-inside text-sm text-gray-600 mt-1 space-y-0.5">' + abgeschlossen.map(s => `<li>${s}</li>`).join('') + '</ul>';
            } else { html += '<p class="text-sm text-gray-500 italic mt-1">Keine</p>'; }

            html += '<h4 class="text-sm font-semibold text-amber-600 mt-4">In Arbeit ('+inArbeit.length+')</h4>';
            if (inArbeit.length > 0) {
                html += '<ul class="list-disc list-inside text-sm text-gray-600 mt-1 space-y-0.5">' + inArbeit.map(s => `<li>${s}</li>`).join('') + '</ul>';
            } else { html += '<p class="text-sm text-gray-500 italic mt-1">Keine</p>'; }

            employeeDetailContent.innerHTML = html;
            employeeDetailCard.classList.remove('hidden');
        }

        function updateFilterSummary(filteredQuals) {
            const summaryContainer = document.getElementById('filterSummaryCard');
            if (employeeFilter.value !== "") {
                summaryContainer.innerHTML = '';
                return;
            }

            const totalEmployees = filteredQuals.length;
            let totalSkillOccurrences = 0;
            let certifiedSkillOccurrences = 0;

            filteredQuals.forEach(q => {
                q.merkmale.forEach(m => {
                    if (m.merkmal.includes('/') && m.wert) {
                        totalSkillOccurrences++;
                        if (m.wert === 'X') {
                            certifiedSkillOccurrences++;
                        }
                    }
                });
            });

            const certificationRate = totalSkillOccurrences > 0 ? Math.round((certifiedSkillOccurrences / totalSkillOccurrences) * 100) : 0;

            summaryContainer.innerHTML = `
                <div class="flex items-center space-x-4 py-2">
                    <div class="p-3 bg-blue-100 rounded-full"><span class="material-icons-outlined text-blue-600">person_search</span></div>
                    <div>
                        <p class="text-sm text-gray-500">Gefundene Mitarbeiter</p>
                        <p class="text-2xl font-bold text-gray-900">${totalEmployees}</p>
                    </div>
                </div>
                <div class="flex items-center space-x-4 py-2">
                    <div class="p-3 bg-yellow-100 rounded-full"><span class="material-icons-outlined text-yellow-600">military_tech</span></div>
                    <div>
                        <p class="text-sm text-gray-500">Zertifizierungsrate</p>
                        <p class="text-2xl font-bold text-gray-900">${certificationRate}%</p>
                    </div>
                </div>
            `;
        }

        function updateActiveFilterTags(selectedGroups, selectedSkills) {
            const tagsContainer = document.getElementById('activeFiltersContainer');
            tagsContainer.innerHTML = ''; 

            selectedGroups.forEach(group => {
                const tag = document.createElement('span');
                tag.className = "inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800";
                tag.textContent = group;
                tagsContainer.appendChild(tag);
            });

            selectedSkills.forEach(skill => {
                const tag = document.createElement('span');
                tag.className = "inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800";
                tag.textContent = skill;
                tagsContainer.appendChild(tag);
            });
        }

        function updateTrainingStatusChart(quals) {
            if (!trainingStatusChart) return;
            if (quals.length === 0) {
                trainingStatusChart.updateSeries([]);
                return;
            }

            const statusCounts = { 'X': 0, 'TS': 0, 'TT': 0, 'TP': 0 };
            quals.forEach(q => {
                q.merkmale.forEach(m => {
                    if (m.merkmal.includes('/') && statusCounts.hasOwnProperty(m.wert)) {
                        statusCounts[m.wert]++;
                    }
                });
            });

            // Die Reihenfolge der Keys muss zur Reihenfolge der zentralen Labels/Farben passen
            const keysInOrder = ['X', 'TS', 'TT', 'TP'];
            const seriesData = keysInOrder.map(key => statusCounts[key]);

            // Der updateOptions-Aufruf ist jetzt viel schlanker, da Labels und Farben schon bekannt sind.
            trainingStatusChart.updateSeries(seriesData);
        }

        function updateSkillDistributionChart(quals, selectedSkills, filterContext = {}) {
            if (!skillDistributionChart) return;
            // Die Logik zum Sammeln der Daten wird erweitert
            const engineData = {}; // Format: { "CF34-10": { "X": { count: 1, names: ["Name A"] }, ... }, ... }

            quals.forEach(q => {
                q.merkmale.forEach(m => {
                    if (m.merkmal.includes('/') && m.wert) {
                        const [engine, skill] = m.merkmal.split('/');
                        if (selectedSkills.length > 0 && !selectedSkills.includes(skill)) {
                            return; 
                        }

                        if (!engineData[engine]) engineData[engine] = {};
                        if (!engineData[engine][m.wert]) engineData[engine][m.wert] = { count: 0, names: new Set() };

                        // Füge den Namen zum Set hinzu, um Duplikate pro Skill/Status/Engine zu vermeiden
                        engineData[engine][m.wert].names.add(q.name);
                    }
                });
            });

            // Aktualisiere die `count`-Eigenschaft basierend auf der Größe des Sets
            Object.values(engineData).forEach(statuses => {
                Object.values(statuses).forEach(data => {
                    data.count = data.names.size;
                });
            });

            const categories = Object.keys(engineData).sort();

            if (categories.length === 0) {
                let noDataMessage = 'Keine passenden Skills in dieser Auswahl gefunden.';
                if (filterContext.isSingleEmployee || filterContext.isGroupOnly) noDataMessage = '';
                skillDistributionChart.updateSeries([], false);
                skillDistributionChart.updateOptions({ xaxis: { categories: [] }, noData: { text: noDataMessage } });
                return;
            }

            const series = [];
            const statuses = ['X', 'TS', 'TT', 'TP'];
            const statusLabels = {'X': 'Abgeschlossen', 'TS': 'Gestartet', 'TT': 'Überfällig', 'TP': 'Geplant'};
            const statusColors = {'X': '#10B981', 'TS': '#F59E0B', 'TT': '#F05252', 'TP': '#6B7280'};

            statuses.forEach(status => {
                series.push({
                    name: statusLabels[status],
                    // Die `data` für die Serie enthält jetzt nur die Anzahl
                    data: categories.map(engine => (engineData[engine]?.[status]?.count || 0))
                });
            });

            skillDistributionChart.updateOptions({
                series: series,
                xaxis: { categories: categories },
                colors: statuses.map(s => statusColors[s]),
                noData: { text: 'Keine Daten zum Anzeigen.' },
                // HIER IST DER NEUE BENUTZERDEFINIERTE TOOLTIP
                tooltip: {
                    custom: function({ series, seriesIndex, dataPointIndex, w }) {
                        const engineName = w.globals.labels[dataPointIndex];
                        const statusLabel = w.globals.seriesNames[seriesIndex];
                        const statusKey = statuses[seriesIndex];

                        const data = engineData[engineName]?.[statusKey];
                        const count = data?.count || 0;

                        if (count === 0) return ''; // Zeige keinen Tooltip für leere Segmente

                        const namesArray = data.names ? Array.from(data.names) : [];
                        const namesHtml = namesArray.slice(0, 10).map(name => `<li>${name}</li>`).join('');
                        const moreCount = namesArray.length > 10 ? `<li>... und ${namesArray.length - 10} weitere</li>` : '';

                        return `<div class="p-2 apexcharts-tooltip-custom">
                                    <div class="font-bold border-b pb-1 mb-1">${engineName} - ${statusLabel}: ${count}</div>
                                    <ul class="list-disc pl-4 text-xs">
                                        ${namesHtml}
                                        ${moreCount}
                                    </ul>
                                </div>`;
                    }
                }
            });
        }

        function updateAvailabilityChart(filteredQuals, selectedSkills) {
            if (!availabilityChart) return;
            // Schritt 1: Erstelle einmalig eine einfache Verfügbarkeits-Map für alle Mitarbeiter
            const availabilityMap = new Map();
            allEmployees.forEach(emp => {
                const empAvail = new Map();
                (emp.weeks || []).forEach(week => {
                    const weekShift = week.Schicht === 'F' ? 'AM' : (week.Schicht === 'S' ? 'PM' : null);
                    if (weekShift) {
                        (week.days || []).forEach(day => {
                            // Die Daten sind jetzt Strings, der Vergleich funktioniert
                            if (day.date && !day.ereignis.trim()) {
                                const key = `${day.date}_${weekShift}`;
                                empAvail.set(key, true);
                            }
                        });
                    }
                });
                availabilityMap.set(emp.id.toLowerCase(), empAvail);
            });

            // Schritt 2: Definiere den Zeitraum und die Zeitschlitze
            const numDays = parseInt(availabilityTimeFilter.value, 10);
            const today = new Date();
            today.setUTCHours(0, 0, 0, 0);
            const todayTimestamp = today.getTime();

            const timeSlots = [];
            for (let i = 0; i < numDays; i++) {
                const dayTimestamp = todayTimestamp + i * 24 * 60 * 60 * 1000;
                timeSlots.push({ timestamp: dayTimestamp, shift: 'AM' });
                timeSlots.push({ timestamp: dayTimestamp + 12 * 60 * 60 * 1000, shift: 'PM' });
            }

            // Schritt 3: Wähle die zu plottenden Skills aus
            let skillsToPlot = new Set();
            if (selectedSkills.length > 0) {
                filteredQuals.forEach(q => {
                    q.merkmale.forEach(m => {
                        if (m.merkmal.includes('/') && selectedSkills.includes(m.merkmal.split('/')[1])) {
                            skillsToPlot.add(m.merkmal);
                        }
                    });
                });
            } else {
                filteredQuals.forEach(q => {
                    q.merkmale.forEach(m => {
                        if (m.merkmal.includes('/')) skillsToPlot.add(m.merkmal);
                    });
                });
            }

            if (skillsToPlot.size === 0) {
                availabilityChart.updateSeries([]);
                availabilityChart.updateOptions({ noData: { text: 'Bitte einen Skill auswählen, um den Trend zu sehen.' } });
                return;
            }

            // Schritt 4: Berechne die Daten für jede Linie
            const series = [];
            skillsToPlot.forEach(merkmal => {
                const countsPerSlot = [];
                const employeeIdsWithSkill = new Set(
                    filteredQuals
                        .filter(q => q.merkmale.some(m => m.merkmal === merkmal && ['X', 'TS', 'TT'].includes(m.wert)))
                        .map(q => q.id.toLowerCase())
                );

                timeSlots.forEach(slot => {
                    let availableCount = 0;
                    const slotDate = new Date(slot.timestamp);
                    const isoDate = `${slotDate.getUTCFullYear()}-${String(slotDate.getUTCMonth() + 1).padStart(2, '0')}-${String(slotDate.getUTCDate()).padStart(2, '0')}`;
                    const availabilityKey = `${isoDate}_${slot.shift}`;

                    employeeIdsWithSkill.forEach(empId => {
                        const empAvail = availabilityMap.get(empId);
                        if (empAvail && empAvail.has(availabilityKey)) {
                            availableCount++;
                        }
                    });

                    countsPerSlot.push({ x: slot.timestamp, y: availableCount });
                });

                if (countsPerSlot.some(p => p.y > 0)) {
                    series.push({ name: merkmal, data: countsPerSlot });
                }
            });

            // Schritt 5: Aktualisiere das Diagramm
            availabilityChart.updateSeries(series);
            availabilityChart.updateOptions({
                xaxis: {
                    type: 'datetime',
                    labels: {
                        formatter: function(value, timestamp) {
                            if (!timestamp) return '';
                            const date = new Date(timestamp);
                            const day = date.toLocaleDateString('de-DE', { weekday: 'short' });
                            // Wichtig: getUTCHours() verwenden, um Zeitzonenfehler zu vermeiden
                            const shift = date.getUTCHours() < 12 ? 'AM' : 'PM';
                            return `${day} ${shift}`;
                        },
                        rotate: -45,
                        hideOverlappingLabels: false,
                        trim: true,
                    },
                    tooltip: {
                        enabled: true,
                        formatter: function (val) {
                             const date = new Date(val);
                             return date.toLocaleDateString('de-DE', {dateStyle: 'medium'}) + (date.getUTCHours() < 12 ? ' Früh' : ' Spät');
                        }
                    }
                },
                tooltip: { x: { show: false } },
                noData: { text: 'Keine Verfügbarkeiten für die aktuelle Auswahl.' }
            });
        }

        function clearAllFilters() {
            employeeFilter.value = "";
            groupCheckboxes.forEach(cb => cb.checked = false);
            skillCheckboxes.forEach(cb => cb.checked = false);
            applyFilters();
        }

        if (employeeFilter) employeeFilter.addEventListener('change', applyFilters);
        groupCheckboxes.forEach(cb => cb.addEventListener('change', applyFilters));
        skillCheckboxes.forEach(cb => cb.addEventListener('change', applyFilters));
        if (availabilityTimeFilter) availabilityTimeFilter.addEventListener('change', applyFilters);
        if (clearFiltersBtn) clearFiltersBtn.addEventListener('click', clearAllFilters);

        if (employeeFilter || availabilityTimeFilter) applyFilters(); // Initialer Aufruf
        
    // --- SFM BOARD LOGIC ---
    // 1. People Count berechnen (aus den vorhandenen Daten)
    const todayDate = new Date();
    const todayIso = todayDate.toISOString().split('T')[0];
    
    // Berechne "Gestern" (bzw. Freitag, wenn heute Montag ist)
    const yesterdayDate = new Date(todayDate);
    let daysBack = 1;
    if (todayDate.getDay() === 1) daysBack = 3; // Wenn Montag, gehe zu Freitag
    if (todayDate.getDay() === 0) daysBack = 2; // Wenn Sonntag, gehe zu Freitag
    yesterdayDate.setDate(todayDate.getDate() - daysBack);
    const yesterdayIso = yesterdayDate.toISOString().split('T')[0];

    let presentCount = 0;
    let yesterdayCount = 0;

    // Wochenende-Check: Sa=6, So=0
    const todayDow = todayDate.getDay();
    const yesterdayDow = yesterdayDate.getDay();
    const todayIsWorkday = todayDow !== 0 && todayDow !== 6;
    const yesterdayIsWorkday = yesterdayDow !== 0 && yesterdayDow !== 6;

    // Gruppen-Filter anwenden (leere Liste = alle Gruppen)
    const employeesForPeopleCount = (sfmPeopleGroupsFilter && sfmPeopleGroupsFilter.length > 0)
        ? allEmployees.filter(e => sfmPeopleGroupsFilter.includes((e.quali || '').trim()))
        : allEmployees;

    if (allEmployees) {
        employeesForPeopleCount.forEach(emp => {
            // Exception-only-Logik: Mitarbeiter gilt als anwesend, außer er hat
            // einen expliziten Abwesenheitseintrag (U, K, etc.) für diesen Tag.
            let absentToday = false;
            let absentYesterday = false;

            (emp.weeks || []).forEach(week => {
                (week.days || []).forEach(day => {
                    let dStr = day.date;
                    if (typeof dStr !== 'string') {
                        try { dStr = new Date(day.date).toISOString().split('T')[0]; } catch(e){}
                    }
                    if (day.ereignis && day.ereignis.trim()) {
                        if (dStr === todayIso) absentToday = true;
                        if (dStr === yesterdayIso) absentYesterday = true;
                    }
                });
            });

            if (todayIsWorkday && !absentToday) presentCount++;
            if (yesterdayIsWorkday && !absentYesterday) yesterdayCount++;
        });
    }
    
    // Werte setzen
    const countEl = document.getElementById('sfm-people-count');
    if(countEl) countEl.textContent = presentCount;

    const totalEl = document.getElementById('sfm-people-total');
    const totalValEl = document.getElementById('sfm-people-total-val');
    if (totalEl && totalValEl) {
        const total = employeesForPeopleCount.length;
        totalValEl.textContent = total;
        totalEl.style.display = total > 0 ? 'inline' : 'none';
    }

    // Diff anzeigen
    const diffEl = document.getElementById('sfm-people-diff');
    if(diffEl) {
        const diff = presentCount - yesterdayCount;
        diffEl.classList.remove('hidden');
        
        if (diff > 0) {
            diffEl.className = "flex items-center text-xs font-bold px-2 py-0.5 rounded-full bg-green-100 text-green-700";
            diffEl.innerHTML = `<span class="material-icons-outlined text-[14px] mr-0.5">trending_up</span> +${diff}`;
        } else if (diff < 0) {
            diffEl.className = "flex items-center text-xs font-bold px-2 py-0.5 rounded-full bg-red-100 text-red-700";
            diffEl.innerHTML = `<span class="material-icons-outlined text-[14px] mr-0.5">trending_down</span> ${diff}`;
        } else {
            diffEl.className = "flex items-center text-xs font-bold px-2 py-0.5 rounded-full bg-slate-100 text-slate-500";
            diffEl.innerHTML = `<span class="material-icons-outlined text-[14px] mr-0.5">remove</span> +/- 0`;
        }
    }
    // --- EFFICIENCY CHART ---
    function buildEfficiencyChartData() {
        // Aktueller Monat: Tag 1 bis heute (täglich)
        const today = new Date();
        const todayIso = today.toISOString().split('T')[0];
        const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
        const monthStartIso = monthStart.toISOString().split('T')[0];

        const recentDates = [], recentLabels = [];
        for (let d = new Date(monthStart); d <= today; d.setDate(d.getDate() + 1)) {
            const iso = d.toISOString().split('T')[0];
            recentDates.push(iso);
            recentLabels.push(new Date(d).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }));
        }

        // Alle vorherigen Monate als Mittelwerte
        const monthlyAverages = {};
        const cutoff = monthStartIso; // Grenze = 1. des aktuellen Monats
        Object.entries(sfmEfficiencyData).forEach(([dateStr, vals]) => {
            if (dateStr >= cutoff) return; // gehört zum aktuellen Monat
            const monthKey = dateStr.substring(0, 7); // YYYY-MM
            if (!monthlyAverages[monthKey]) monthlyAverages[monthKey] = { IGT:[], CFX:[], PWC:[], Gesamt:[] };
            ['IGT','CFX','PWC','Gesamt'].forEach(k => {
                if (vals[k] != null) monthlyAverages[monthKey][k].push(vals[k]);
            });
        });

        const avg = arr => arr.length ? Math.round(arr.reduce((a,b)=>a+b,0)/arr.length*10)/10 : null;
        const sortedMonths = Object.keys(monthlyAverages).sort();
        const monthLabels = sortedMonths.map(m => {
            const [y, mo] = m.split('-');
            return new Date(+y, +mo-1).toLocaleDateString('de-DE', { month: 'short', year: '2-digit' });
        });

        const allLabels = [...monthLabels, ...recentLabels];
        const monthCount = monthLabels.length;

        // Daten pro Kurve zusammenbauen
        const series = ['IGT','CFX','PWC','Gesamt'].map((key, ki) => {
            const monthVals = sortedMonths.map(m => avg(monthlyAverages[m][key]));
            const dayVals = recentDates.map(iso => {
                const v = sfmEfficiencyData[iso]?.[key];
                return (v != null) ? v : null;
            });
            return { name: key, data: [...monthVals, ...dayVals] };
        });

        return { series, allLabels, monthCount };
    }

    function renderEfficiencyChart() {
        const chartEl = document.getElementById('efficiencyChart');
        if (!chartEl || typeof ApexCharts === 'undefined') return;

        const { series, allLabels, monthCount } = buildEfficiencyChartData();

        // Badge aktualisieren
        const badge = document.getElementById('efficiencyTargetBadge');
        if (badge) badge.textContent = `Ziel: > ${sfmEfficiencyTarget}%`;

        const colors = ['#3b82f6', '#10b981', '#f97316', '#8b5cf6'];
        const strokeWidths = [2, 2, 2, 3];
        const dashArrays = [0, 0, 0, 5];

        const options = {
            series,
            chart: {
                type: 'line',
                height: 288,
                fontFamily: 'Inter, sans-serif',
                toolbar: { show: false },
                zoom: { enabled: false },
                animations: { enabled: false }
            },
            dataLabels: { enabled: false },
            stroke: { curve: 'smooth', width: strokeWidths, dashArray: dashArrays },
            colors,
            markers: { size: 3, hover: { size: 6 } },
            xaxis: {
                categories: allLabels,
                labels: { style: { fontSize: '10px', colors: '#64748b' }, rotate: -30 },
                tooltip: { enabled: false },
                axisBorder: { show: false },
                // Trennlinie zwischen Monaten und Tagen
                plotOptions: {}
            },
            yaxis: {
                min: 40, max: 100,
                labels: { formatter: v => v != null ? v.toFixed(0) + '%' : '' }
            },
            grid: { borderColor: '#f1f5f9', strokeDashArray: 4 },
            legend: { position: 'top', fontSize: '12px', markers: { width: 10, height: 10, radius: 10 } },
            tooltip: { y: { formatter: v => v != null ? v.toFixed(1) + '%' : 'k.A.' } },
            annotations: {
                xaxis: monthCount > 0 ? [{
                    x: allLabels[monthCount - 1],
                    x2: allLabels[monthCount],
                    fillColor: '#f1f5f9',
                    opacity: 0.3,
                    label: { text: '◀ Monatsmittel', style: { fontSize: '9px', color: '#94a3b8' } }
                }] : [],
                yaxis: [{
                    y: sfmEfficiencyTarget,
                    borderColor: '#10b981',
                    strokeDashArray: 6,
                    label: {
                        borderColor: '#10b981',
                        style: { color: '#fff', background: '#10b981', fontSize: '10px' },
                        text: `Ziel (${sfmEfficiencyTarget}%)`
                    }
                }]
            }
        };

        if (efficiencyChartInstance) {
            efficiencyChartInstance.destroy();
        }
        efficiencyChartInstance = new ApexCharts(chartEl, options);
        efficiencyChartInstance.render();
    }

    renderEfficiencyChart();

    window.saveSfmNotes = async function() {
        const text = document.getElementById('sfm-whiteboard').value;
        const payload = {
            key: 'sfm_board_notes',
            text: text,
            type: 'info', // Default
            visible: true
        };

        try {
            const response = await fetch("{{ url_for('api_save_sfm_text') }}", {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            });
            if (response.ok) {
                alert("Notizen gespeichert.");
            } else {
                alert("Fehler beim Speichern.");
            }
        } catch (e) {
            console.error(e);
            alert("Netzwerkfehler.");
        }
    };
    });

    // Globale Variable für die Daten
    let sfmCalendarData = {{ sfm_calendar_data | tojson }};
    
    // Debounce Helper (wie besprochen)
    function debounce(func, timeout = 1000) {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => { func.apply(this, args); }, timeout);
        };
    }
    
    // Globaler Auto-Saver
    const triggerAutoSave = debounce(() => saveSfmCalendar(true), 1000);
    
    function initSfmCalendar() {
        const header = document.getElementById('sfmCalendarHeader');
        const body = document.getElementById('sfmCalendarBody');
                const keys = ['general', 'info', 'quality_safety', 'success_escalation'];
        keys.forEach(key => {
            if(!Array.isArray(sfmCalendarData[key])) sfmCalendarData[key] = [];
            renderTasksForDate(key);
        });
        const dateTitleEl = document.getElementById('sfm-current-date-title');
        if (dateTitleEl) {
            const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
            dateTitleEl.textContent = new Date().toLocaleDateString('de-DE', options);
        }
        
        // Initialisiere auch das Backlog
        if(!Array.isArray(sfmCalendarData['general'])) {
            sfmCalendarData['general'] = [];
        }
        renderTasksForDate('general');
    
        if(!header || !body) return;
    
        // --- Datumsberechnung ---
        const today = new Date();
        const currentDay = today.getDay() || 7; // 1-7 (Mo-So)
        const mondayThisWeek = new Date(today);
        mondayThisWeek.setDate(today.getDate() - currentDay + 1);
        
        const startDate = new Date(mondayThisWeek);
    
        // Hilfsvariable für den "Heute"-Vergleich (String YYYY-MM-DD)
        const todayIsoStr = today.toISOString().split('T')[0];
    
        header.innerHTML = '<th class="p-3 border-r w-24 bg-slate-100 sticky left-0 z-10 border-b border-slate-200">KW</th>';
        const dayLabels = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
        dayLabels.forEach(d => header.innerHTML += `<th class="p-3 text-center border-r min-w-[140px] bg-slate-50 border-b border-slate-200">${d}</th>`);
    
        body.innerHTML = '';
        
        for (let w = 0; w < 2; w++) {
            const tr = document.createElement('tr');
            const weekStart = new Date(startDate);
            weekStart.setDate(startDate.getDate() + (w * 7));
            
            // KW berechnen
            const tempDate = new Date(weekStart);
            tempDate.setDate(tempDate.getDate() + 4 - (tempDate.getDay() || 7));
            const yearStart = new Date(tempDate.getFullYear(), 0, 1);
            const kw = Math.ceil((((tempDate - yearStart) / 86400000) + 1) / 7);
            
            let label = `KW ${kw}`;
            let subLabel = w === 0 ? "Aktuell" : "Nächste";
    
            let rowHtml = `
                <td class="p-3 font-bold text-slate-500 bg-slate-50 border-r text-center sticky left-0 z-10 shadow-[2px_0_5px_-2px_rgba(0,0,0,0.05)] align-middle">
                    <div class="text-lg">${kw}</div>
                    <div class="text-[10px] uppercase tracking-wide text-slate-400 font-normal">${subLabel}</div>
                </td>`;
            
            for (let d = 0; d < 7; d++) {
                const date = new Date(weekStart);
                date.setDate(weekStart.getDate() + d);
                const dateIso = date.toISOString().split('T')[0];
                
                // --- NEUE FARB-LOGIK ---
                const isToday = dateIso === todayIsoStr;
                const isPast = dateIso < todayIsoStr;
                const isWeekend = date.getDay() === 0 || date.getDay() === 6; // 0=So, 6=Sa
    
                let bgClass = 'bg-white'; // Standard Zukunft Werktag
                
                if (isToday) {
                    bgClass = 'bg-amber-50'; // Heute (Prio 1)
                } else if (isPast) {
                    bgClass = 'bg-slate-100'; // Vergangenheit (Prio 2) - etwas dunkleres Grau
                } else if (isWeekend) {
                    bgClass = 'bg-slate-50'; // Zukunft Wochenende (Prio 3) - ganz leichtes Grau
                }
                // -----------------------
                
                if (!Array.isArray(sfmCalendarData[dateIso])) {
                    sfmCalendarData[dateIso] = [];
                }
    
                rowHtml += `
                    <td class="p-0 border-r ${bgClass} align-top h-40">
                        <div class="flex flex-col h-full relative group/cell">
                            <!-- Datum Header -->
                            <div class="text-[10px] p-1.5 text-slate-400 font-bold flex justify-between items-center select-none border-b border-dashed border-slate-200/50">
                                <span>${date.getDate()}.${date.getMonth()+1}.</span>
                                ${isToday ? '<span class="text-amber-600 bg-amber-100 px-1.5 rounded text-[9px]">HEUTE</span>' : ''}
                            </div>
                            
                            <!-- Task Liste -->
                            <div class="flex-grow overflow-y-auto custom-scrollbar p-1 space-y-1" id="taskList-${dateIso}">
                                <!-- Wird per JS gefüllt -->
                            </div>
                            
                            <!-- Input Feld -->
                            <div class="p-1 border-t border-slate-100 mt-auto opacity-0 group-hover/cell:opacity-100 focus-within:opacity-100 transition-opacity bg-white/80 backdrop-blur-sm">
                                <input type="text" 
                                    class="w-full text-xs border-0 bg-transparent p-1 focus:ring-0 placeholder-slate-400 text-slate-700" 
                                    placeholder="+ Eintrag..." 
                                    onkeydown="if(event.key === 'Enter') { addTask('${dateIso}', this.value); this.value = ''; }">
                            </div>
                        </div>
                    </td>`;
                
                setTimeout(() => renderTasksForDate(dateIso), 0);
            }
            tr.innerHTML = rowHtml;
            body.appendChild(tr);
        }
    }
    
    function renderTasksForDate(listKey) {
        const container = document.getElementById(`taskList-${listKey}`);
        if (!container) return;
        
        const tasks = sfmCalendarData[listKey] || [];
        container.innerHTML = '';
    
        tasks.forEach((task, index) => {
            // Basis-Klassen
            let containerClass = "task-item flex items-start p-1.5 rounded hover:bg-black/5 group text-xs transition-colors mb-1 bg-white border border-transparent";
            
            // Spezial-Styling
            if (task.type === 'success') {
                containerClass += " !bg-green-50 !border-green-200 border-l-4 !border-l-green-500 pl-2";
            } else if (task.type === 'escalation') {
                containerClass += " !bg-red-50 !border-red-200 border-l-4 !border-l-red-500 pl-2";
            }
    
            const div = document.createElement('div');
            div.className = containerClass;
            // WICHTIG für SortableJS: Index speichern
            div.dataset.index = index;
            let backlogChip = null, backlogPopover = null;
            
            // 1. Drag Handle (Neu)
            const handle = document.createElement('span');
            handle.className = "material-icons-outlined text-[14px] drag-handle mt-0.5 opacity-0 group-hover:opacity-100 transition-opacity";
            handle.textContent = "drag_indicator";
    
            // 2. Checkbox
            const checkbox = document.createElement('input');
            checkbox.type = "checkbox";
            checkbox.className = "task-checkbox mt-0.5 w-3.5 h-3.5 rounded border-slate-300 text-blue-600 focus:ring-blue-500 cursor-pointer flex-shrink-0 mr-2";
            checkbox.checked = task.done;
            checkbox.onchange = () => toggleTask(listKey, index);
            
            // 3. Content Wrapper
            const contentWrapper = document.createElement('div');
            contentWrapper.className = "flex-grow min-w-0 flex flex-col";
    
            // Badges (wie gehabt)
            if (task.type === 'success') contentWrapper.innerHTML += `<span class="block text-[9px] font-bold text-green-700 uppercase tracking-wider mb-0.5">Erfolg</span>`;
            else if (task.type === 'escalation') contentWrapper.innerHTML += `<span class="block text-[9px] font-bold text-red-700 uppercase tracking-wider mb-0.5">Eskalation</span>`;
    
            // Text (Editierbar)
            const span = document.createElement('span');
            span.className = `block break-words outline-none ${task.done ? 'text-slate-400 line-through' : 'text-slate-700'}`;
            span.contentEditable = true;
            span.textContent = task.text;
            
            // Update Logik (Text + Datum)
            span.oninput = () => {
                tasks[index].text = span.textContent;
                // Datum aktualisieren
                tasks[index].updatedAt = new Date().toLocaleDateString('de-DE');
                triggerAutoSave();
            };
            // Blur beim Enter, um Fokus zu verlieren (Datum wird beim input schon gespeichert)
            span.onkeydown = (e) => {
                if (e.key === 'Enter') { e.preventDefault(); span.blur(); }
            };
    
            // Datums-Anzeige (Neu)
            const dateInfo = document.createElement('div');
            dateInfo.className = "text-[9px] text-slate-400 mt-1 flex justify-end gap-2 opacity-60 group-hover:opacity-100";
            // Zeige "Bearbeitet" Datum wenn vorhanden und ungleich Erstellt, sonst Erstellt
            let dateText = task.createdAt || "";
            if (task.updatedAt && task.updatedAt !== task.createdAt) {
                dateText = `Ed. ${task.updatedAt}`;
            }
            dateInfo.textContent = dateText;
    
            contentWrapper.appendChild(span);
            contentWrapper.appendChild(dateInfo);

            // Backlog-Extras: kombinierter Chip + Popover (nur für 'general')
            if (listKey === 'general') {
                div.classList.add('relative');

                function fmtDate(iso) {
                    if (!iso) return '';
                    const d = new Date(iso);
                    return String(d.getDate()).padStart(2,'0') + '.' + String(d.getMonth()+1).padStart(2,'0') + '.';
                }

                const chip = document.createElement('button');
                chip.type = 'button';
                chip.className = "flex-shrink-0 rounded-full border px-1.5 text-[10px] font-medium leading-snug transition-colors ml-1 opacity-50 group-hover:opacity-100 whitespace-nowrap";

                function refreshChip() {
                    const parts = [];
                    if (tasks[index].assignee) parts.push(tasks[index].assignee);
                    if (tasks[index].dueDate) parts.push(fmtDate(tasks[index].dueDate));
                    chip.textContent = parts.join(' · ') || '+';
                    chip.classList.remove(
                        'bg-slate-50','border-slate-200','text-slate-400',
                        'bg-blue-50','border-blue-200','text-blue-700',
                        'bg-amber-50','border-amber-300','text-amber-700',
                        'bg-red-50','border-red-300','text-red-700'
                    );
                    const due = tasks[index].dueDate;
                    if (!due || tasks[index].done) {
                        chip.classList.add('bg-slate-50','border-slate-200','text-slate-400');
                    } else {
                        const today = new Date(); today.setHours(0,0,0,0);
                        const diff = Math.round((new Date(due) - today) / 86400000);
                        if (diff < 0)       chip.classList.add('bg-red-50','border-red-300','text-red-700');
                        else if (diff <= 3) chip.classList.add('bg-amber-50','border-amber-300','text-amber-700');
                        else                chip.classList.add('bg-blue-50','border-blue-200','text-blue-700');
                    }
                }
                refreshChip();

                const popover = document.createElement('div');
                popover.className = "backlog-popover absolute right-0 top-full mt-1 z-50 bg-white border border-slate-200 rounded-xl shadow-lg p-3 hidden";
                popover.style.minWidth = "170px";
                popover.onclick = e => e.stopPropagation();

                const dlabel = document.createElement('div');
                dlabel.className = "text-[10px] text-slate-400 font-medium mb-0.5";
                dlabel.textContent = "Zieldatum";
                const popDate = document.createElement('input');
                popDate.type = "date";
                popDate.className = "w-full text-xs border border-slate-200 rounded-lg px-2 py-1 mb-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent";
                popDate.value = tasks[index].dueDate || "";
                popDate.onchange = () => { tasks[index].dueDate = popDate.value; refreshChip(); triggerAutoSave(); };

                const alabel = document.createElement('div');
                alabel.className = "text-[10px] text-slate-400 font-medium mb-0.5";
                alabel.textContent = "Mitarbeiter";
                const popSelect = document.createElement('select');
                popSelect.className = "w-full text-xs border border-slate-200 rounded-lg px-2 py-1 bg-white focus:ring-2 focus:ring-blue-500 focus:border-transparent";
                const emptyOpt2 = document.createElement('option');
                emptyOpt2.value = ""; emptyOpt2.textContent = "— kein —";
                popSelect.appendChild(emptyOpt2);
                const employees = {{ backlog_employees | tojson }};
                employees.forEach(name => {
                    const opt = document.createElement('option');
                    opt.value = name; opt.textContent = name;
                    if (name === tasks[index].assignee) opt.selected = true;
                    popSelect.appendChild(opt);
                });
                popSelect.onchange = () => { tasks[index].assignee = popSelect.value; refreshChip(); triggerAutoSave(); };

                popover.appendChild(dlabel);
                popover.appendChild(popDate);
                popover.appendChild(alabel);
                popover.appendChild(popSelect);

                chip.onclick = e => {
                    e.stopPropagation();
                    const wasHidden = popover.classList.contains('hidden');
                    document.querySelectorAll('.backlog-popover').forEach(p => p.classList.add('hidden'));
                    if (wasHidden) {
                        popover.classList.remove('hidden');
                        setTimeout(() => document.addEventListener('click', () => popover.classList.add('hidden'), { once: true }), 0);
                    }
                };

                backlogChip = chip;
                backlogPopover = popover;
            }

            // 4. Delete Button
            const delBtn = document.createElement('button');
            delBtn.className = "task-delete-btn text-slate-300 hover:text-red-500 transition-colors ml-1 opacity-0 group-hover:opacity-100";
            delBtn.innerHTML = '<span class="material-icons-outlined text-[14px]">close</span>';
            delBtn.onclick = () => deleteTask(listKey, index);

            div.appendChild(handle);
            div.appendChild(checkbox);
            div.appendChild(contentWrapper);
            if (backlogPopover) div.appendChild(backlogPopover);
            if (backlogChip) div.appendChild(backlogChip);
            div.appendChild(delBtn);
            container.appendChild(div);
        });
    
        // --- SORTABLE INITIALISIEREN ---
        // Wir erstellen jedes Mal eine neue Instanz, da das DOM neu gebaut wird.
        // SortableJS ist sehr leichtgewichtig.
        if (typeof Sortable !== 'undefined') {
            new Sortable(container, {
                handle: '.drag-handle', // Nur am Griff ziehbar (wichtig damit Text markierbar bleibt)
                animation: 150,
                ghostClass: 'sortable-ghost',
                onEnd: function (evt) {
                    // Array umstrukturieren
                    const item = tasks.splice(evt.oldIndex, 1)[0];
                    tasks.splice(evt.newIndex, 0, item);
                    
                    // Wir müssen nicht neu rendern, da das DOM schon stimmt, 
                    // aber wir müssen speichern.
                    triggerAutoSave();
                    
                    // Optional: Re-Render aufrufen, um Indizes in den Event-Listenern (Delete/Toggle) zu korrigieren.
                    // Da unsere Closures (index) beim Erstellen gebunden werden, stimmen sie nach dem Drop nicht mehr!
                    // DESHALB: Re-Render zwingend notwendig.
                    renderTasksForDate(listKey);
                }
            });
        }
    }
    
    // --- INTERAKTIONS-LOGIK ---

    function updateDueDateStyle(pillEl, dateValue, isDone) {
        pillEl.classList.remove(
            'bg-blue-50','border-blue-200','text-blue-700',
            'bg-red-50','border-red-300','text-red-700',
            'bg-amber-50','border-amber-300','text-amber-700'
        );
        if (!dateValue || isDone) {
            pillEl.classList.add('bg-blue-50','border-blue-200','text-blue-700');
            return;
        }
        const today = new Date(); today.setHours(0,0,0,0);
        const diffDays = Math.round((new Date(dateValue) - today) / 86400000);
        if (diffDays < 0) {
            pillEl.classList.add('bg-red-50','border-red-300','text-red-700');
        } else if (diffDays <= 3) {
            pillEl.classList.add('bg-amber-50','border-amber-300','text-amber-700');
        } else {
            pillEl.classList.add('bg-blue-50','border-blue-200','text-blue-700');
        }
    }

    function addBacklogTask() {
        const textEl = document.getElementById('backlog-new-text');
        const dueEl = document.getElementById('backlog-new-due');
        const assigneeEl = document.getElementById('backlog-new-assignee');
        const text = textEl.value.trim();
        if (!text) return;
        if (!sfmCalendarData['general']) sfmCalendarData['general'] = [];
        const todayStr = new Date().toLocaleDateString('de-DE');
        const newTask = { text, done: false, createdAt: todayStr, updatedAt: todayStr };
        if (dueEl.value) newTask.dueDate = dueEl.value;
        if (assigneeEl.value) newTask.assignee = assigneeEl.value;
        sfmCalendarData['general'].push(newTask);
        textEl.value = ''; dueEl.value = ''; assigneeEl.value = '';
        renderTasksForDate('general');
        triggerAutoSave();
    }

    function addTask(listKey, text, type = null) {
        if (!text.trim()) return;
        
        if (!sfmCalendarData[listKey]) sfmCalendarData[listKey] = [];
        
        const todayStr = new Date().toLocaleDateString('de-DE'); // Nur Datum, keine Zeit
    
        const newTask = {
            text: text.trim(),
            done: false,
            createdAt: todayStr, // NEU
            updatedAt: todayStr  // NEU
        };
        
        if (type) newTask.type = type;
        
        sfmCalendarData[listKey].push(newTask);
        
        renderTasksForDate(listKey);
        triggerAutoSave();
    }

    function addSpecialTask(type) {
        const input = document.getElementById('input-success_escalation');
        const text = input.value;
        if(text) {
            addTask('success_escalation', text, type);
            input.value = '';
        }
    }
    
    function toggleTask(dateIso, index) {
        if (sfmCalendarData[dateIso] && sfmCalendarData[dateIso][index]) {
            sfmCalendarData[dateIso][index].done = !sfmCalendarData[dateIso][index].done;
            renderTasksForDate(dateIso); // Re-Render für Durchstreichen-Effekt
            triggerAutoSave();
        }
    }
    
    function deleteTask(dateIso, index) {
        if (sfmCalendarData[dateIso]) {
            sfmCalendarData[dateIso].splice(index, 1);
            renderTasksForDate(dateIso);
            triggerAutoSave();
        }
    }
    
    // --- SPEICHERN ---
    async function saveSfmCalendar(isAutoSave = false) {
        try {
            const res = await fetch('/api/save_sfm_calendar', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(sfmCalendarData)
            });
            
            if(!res.ok) {
                console.error("Auto-Save failed");
            } else {
                // Success Feedback (Indikator aufleuchten lassen)
                const indicator = document.getElementById('autoSaveIndicator');
                if(indicator) {
                    indicator.classList.remove('opacity-0');
                    setTimeout(() => indicator.classList.add('opacity-0'), 2000);
                }
            }
        } catch(e) { 
            console.error("Network error during save:", e); 
        }
    }


    initSfmCalendar();
    function switchEmpTab(name, btn) {
        document.querySelectorAll('.emp-tab-content').forEach(c => c.classList.add('hidden'));
        const target = document.getElementById('view-' + name);
        if(target) target.classList.remove('hidden');
        
        document.querySelectorAll('.emp-tab-btn').forEach(b => {
            b.classList.remove('border-blue-500', 'text-blue-600');
            b.classList.add('border-transparent', 'text-slate-500');
        });
        btn.classList.remove('border-transparent', 'text-slate-500');
        btn.classList.add('border-blue-500', 'text-blue-600');
        
        if(name === 'matrix') loadSkillMatrix();
        if(name === 'sfm') initSfmCalendar();
        if(name === 'projektplan') loadProjektplan();
    }
    
    // ── PROJEKTPLAN ─────────────────────────────────────────────────────────
    const PLAN_COLORS = {
        'RIG':      { bg:'#FDF2F8', border:'#EC4899', text:'#9D174D' },
        'DE-RIG':   { bg:'#FDF2F8', border:'#EC4899', text:'#9D174D' },
        'TEST':     { bg:'#FFF7ED', border:'#F97316', text:'#9A3412' },
        'ATW':      { bg:'#F8FAFC', border:'#64748B', text:'#334155' },
        'BSI':      { bg:'#EFF6FF', border:'#3B82F6', text:'#1E40AF' },
        'INACTIVE': { bg:'#F9FAFB', border:'#9CA3AF', text:'#6B7280' },
    };
    const PLAN_DEFAULT_COLOR = { bg:'#F5F3FF', border:'#8B5CF6', text:'#5B21B6' };

    function planColor(title) {
        return PLAN_COLORS[title.toUpperCase()] || PLAN_DEFAULT_COLOR;
    }

    function fmtDate(iso) {
        if (!iso) return '';
        const [, m, d] = iso.split('-');
        return `${d}.${m}`;
    }

    async function loadProjektplan() {
        const container = document.getElementById('projektplan-container');
        container.innerHTML = '<div class="text-slate-400 italic text-sm p-4 flex items-center gap-2"><span class="material-icons-outlined text-sm animate-spin">refresh</span> Lade Projektplan...</div>';
        try {
            const res = await fetch('/api/projektplan');
            if (!res.ok) throw new Error('Server error');
            const timelines = await res.json();
            renderProjektplan(timelines, container);
        } catch(e) {
            container.innerHTML = '<p class="text-red-500 text-sm p-4">Fehler beim Laden des Projektplans.</p>';
        }
    }

    function renderProjektplan(timelines, container) {
        container.innerHTML = '';
        const today = new Date().toISOString().slice(0, 10);
        const todayFmt = new Date().toLocaleDateString('de-DE', {day:'2-digit', month:'2-digit'});

        if (!timelines.length) {
            container.innerHTML = '<p class="text-slate-400 italic text-sm p-4">Keine Timelines konfiguriert.</p>';
            return;
        }

        // Outer scroll wrapper
        const wrapper = document.createElement('div');
        wrapper.className = 'overflow-x-auto rounded-xl border border-slate-200 shadow-sm';

        // Main grid – 5 columns: Engine | Past | Today | Future | Notes
        const grid = document.createElement('div');
        grid.style.cssText = 'display:grid; grid-template-columns: 210px 1fr 180px 1fr 200px; min-width: 880px;';

        // ── Sticky header row ──────────────────────────────────────────────────
        const makeHeaderCell = (text, extra, style) => {
            const c = document.createElement('div');
            c.className = `px-3 py-2 text-xs font-semibold uppercase tracking-wide border-b ${extra}`;
            if (style) c.style.cssText = style;
            c.textContent = text;
            return c;
        };
        grid.appendChild(makeHeaderCell('Anlage / Motor', 'text-slate-500 border-r border-slate-200 bg-white'));
        grid.appendChild(makeHeaderCell('Vergangen', 'text-slate-400 border-r border-slate-200 bg-white text-right'));
        const todayHeader = makeHeaderCell(`HEUTE  ${todayFmt}`, 'text-white text-center border-b border-blue-600', 'background:#2563EB');
        grid.appendChild(todayHeader);
        grid.appendChild(makeHeaderCell('Geplant', 'text-slate-500 border-r border-slate-200 bg-white'));
        grid.appendChild(makeHeaderCell('Notizen', 'text-slate-500 bg-white'));

        // ── Per-timeline sections ───────────────────────────────────────────────
        timelines.forEach(tl => {
            // Section header spanning all 4 columns
            const secRow = document.createElement('div');
            secRow.style.gridColumn = '1 / -1';
            secRow.className = 'flex items-center gap-2 px-4 py-2 bg-slate-100 border-b border-t border-slate-200';
            const icon = document.createElement('span');
            icon.className = 'material-icons-outlined text-slate-500';
            icon.style.fontSize = '16px';
            icon.textContent = 'account_tree';
            secRow.appendChild(icon);
            const label = document.createElement('span');
            label.className = 'text-sm font-bold text-slate-700 uppercase tracking-wide';
            label.textContent = tl.name;
            secRow.appendChild(label);
            grid.appendChild(secRow);

            if (!tl.projects.length) {
                const empty = document.createElement('div');
                empty.style.cssText = 'grid-column: 1 / -1';
                empty.className = 'px-4 py-3 text-slate-400 italic text-sm border-b border-slate-100 bg-white';
                empty.textContent = 'Keine aktiven Projekte.';
                grid.appendChild(empty);
                return;
            }

            tl.projects.forEach((proj, idx) => {
                const rowBg = idx === 0 ? '#EFF6FF' : '#FFFFFF';
                const borderB = 'border-b border-slate-100';

                // Classify milestones
                const pastMs    = (proj.milestones || []).filter(m => m.iso < today);
                const todayMs   = (proj.milestones || []).filter(m => m.iso === today);
                const futureMs  = (proj.milestones || []).filter(m => m.iso > today);

                // Classify events using real_end (exclusive-end convention corrected)
                const pastEvts   = proj.events.filter(e => e.real_end < today);
                const activeEvts = proj.events.filter(e => e.is_current);
                // Future: strictly future events + multi-day active events that extend beyond today
                const futureEvts = proj.events.filter(e => e.real_end > today && e.start > today)
                    .concat(proj.events.filter(e => e.is_current && e.real_end > today));

                // ── Col 1: Engine info ──
                const col1 = document.createElement('div');
                col1.className = `${borderB} border-r border-slate-200 px-3 py-3 flex flex-col gap-0.5 min-w-0`;
                col1.style.background = rowBg;
                const et = document.createElement('p');
                et.className = 'font-bold text-slate-800 text-sm truncate';
                et.textContent = proj.engine_type;
                col1.appendChild(et);
                const sn = document.createElement('p');
                sn.className = 'text-xs text-slate-500 truncate';
                sn.textContent = proj.engine_serial + ' / ' + proj.project_code;
                col1.appendChild(sn);
                const cu = document.createElement('p');
                cu.className = 'text-xs text-slate-400 truncate';
                cu.textContent = proj.customer || '–';
                col1.appendChild(cu);
                const saMs = (proj.milestones || []).find(m => m.key === 'SA');
                const lastEvtEnd = proj.events && proj.events.length ? proj.events.map(e => e.real_end).filter(Boolean).sort().at(-1) : null;
                if (saMs && lastEvtEnd) {
                    const diffDays = Math.round((new Date(saMs.iso) - new Date(lastEvtEnd)) / 86400000);
                    const badge = document.createElement('span');
                    badge.textContent = `SA ${diffDays > 0 ? '+' : ''}${diffDays}d`;
                    if (diffDays < 0) badge.className = 'inline-flex items-center px-1.5 py-0.5 rounded text-xs font-bold bg-red-100 text-red-700 border border-red-200';
                    else if (diffDays === 0) badge.className = 'inline-flex items-center px-1.5 py-0.5 rounded text-xs font-bold bg-gray-100 text-gray-500 border border-gray-200';
                    else badge.className = 'inline-flex items-center px-1.5 py-0.5 rounded text-xs font-bold bg-green-100 text-green-700 border border-green-200';
                    col1.appendChild(badge);
                }
                if (proj.progress !== null && proj.progress !== undefined) {
                    const bar = document.createElement('div');
                    bar.className = 'flex items-center gap-1.5 mt-1';
                    bar.innerHTML = `<div class="flex-1 h-1.5 bg-slate-200 rounded-full overflow-hidden" style="max-width:90px"><div class="h-full bg-blue-500 rounded-full" style="width:${proj.progress}%"></div></div><span class="text-xs font-bold text-slate-400">${proj.progress}%</span>`;
                    col1.appendChild(bar);
                }

                // ── Col 2: Past ──
                const col2 = document.createElement('div');
                col2.className = `${borderB} border-r border-slate-200 px-3 py-3 flex flex-col gap-1.5`;
                col2.style.background = rowBg;
                const col2Evts = document.createElement('div');
                col2Evts.className = 'flex flex-wrap gap-1.5 justify-end';
                pastEvts.forEach(evt => {
                    const chip = document.createElement('span');
                    chip.className = 'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-400 border border-gray-200';
                    const sf = fmtDate(evt.start), ef = fmtDate(evt.real_end);
                    chip.textContent = `${evt.title} ${sf === ef ? sf : sf + '–' + ef}`;
                    col2Evts.appendChild(chip);
                });
                const col2Ms = document.createElement('div');
                col2Ms.className = 'flex flex-wrap gap-1.5 justify-end';
                pastMs.forEach(m => {
                    const chip = document.createElement('span');
                    chip.className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-400 border border-gray-200';
                    chip.textContent = `◆ ${m.label} ${m.value}`;
                    col2Ms.appendChild(chip);
                });
                if (!pastEvts.length && !pastMs.length) {
                    const dash = document.createElement('span');
                    dash.className = 'text-xs text-slate-200 self-center text-right';
                    dash.textContent = '–';
                    col2.appendChild(dash);
                } else {
                    if (pastEvts.length) col2.appendChild(col2Evts);
                    if (pastMs.length) col2.appendChild(col2Ms);
                }

                // ── Col 3: TODAY ──
                const col3 = document.createElement('div');
                col3.className = `border-b border-blue-200 px-3 py-3 flex flex-col gap-1.5 items-center justify-center`;
                col3.style.background = '#EFF6FF';
                if (activeEvts.length === 0 && todayMs.length === 0) {
                    const sb = document.createElement('span');
                    sb.className = 'text-xs font-medium uppercase tracking-wider';
                    sb.style.color = '#93C5FD';
                    sb.textContent = 'STANDBY';
                    col3.appendChild(sb);
                } else {
                    if (activeEvts.length) {
                        const col3Evts = document.createElement('div');
                        col3Evts.className = 'flex flex-col gap-1 w-full';
                        activeEvts.forEach(item => {
                            const block = document.createElement('div');
                            block.className = 'flex items-center gap-1 px-2 py-1 rounded-full text-xs font-bold w-full justify-center border';
                            const c = planColor(item.title);
                            block.style.cssText = `background:${c.bg};border-color:${c.border};color:${c.text}`;
                            const dot = document.createElement('span');
                            dot.style.cssText = `width:6px;height:6px;border-radius:50%;background:${c.border};flex-shrink:0;display:inline-block`;
                            block.appendChild(dot);
                            block.appendChild(document.createTextNode(item.title));
                            col3Evts.appendChild(block);
                        });
                        col3.appendChild(col3Evts);
                    }
                    if (todayMs.length) {
                        const col3Ms = document.createElement('div');
                        col3Ms.className = 'flex flex-col gap-1 w-full';
                        todayMs.forEach(item => {
                            const block = document.createElement('div');
                            block.className = 'flex items-center gap-1 px-2 py-1 rounded text-xs font-bold w-full justify-center border';
                            block.style.cssText = 'background:#2563EB;color:#fff;border-color:#1D4ED8';
                            block.textContent = `◆ ${item.label} ${item.value}`;
                            col3Ms.appendChild(block);
                        });
                        col3.appendChild(col3Ms);
                    }
                }

                // ── Col 4: Future ──
                const col4 = document.createElement('div');
                col4.className = `${borderB} border-r border-slate-200 px-3 py-3 flex flex-col gap-1.5`;
                col4.style.background = rowBg;
                const col4Evts = document.createElement('div');
                col4Evts.className = 'flex flex-wrap gap-1.5';
                futureEvts.forEach(evt => {
                    const c = planColor(evt.title);
                    const chip = document.createElement('span');
                    chip.className = 'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border';
                    chip.style.cssText = `background:${c.bg};border-color:${c.border};color:${c.text}`;
                    const dispStart = evt.is_current ? today : evt.start;
                    const sf = fmtDate(dispStart), ef = fmtDate(evt.real_end);
                    const suffix = evt.is_current ? '→ ' : '';
                    chip.textContent = `${suffix}${evt.title} ${sf === ef ? sf : sf + '–' + ef}`;
                    col4Evts.appendChild(chip);
                });
                const col4Ms = document.createElement('div');
                col4Ms.className = 'flex flex-wrap gap-1.5';
                futureMs.forEach(m => {
                    const chip = document.createElement('span');
                    chip.className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 text-blue-700 border border-blue-200';
                    chip.textContent = `◆ ${m.label} ${m.value}`;
                    col4Ms.appendChild(chip);
                });
                if (!futureEvts.length && !futureMs.length) {
                    const dash = document.createElement('span');
                    dash.className = 'text-xs text-slate-200 self-center';
                    dash.textContent = '–';
                    col4.appendChild(dash);
                } else {
                    if (futureEvts.length) col4.appendChild(col4Evts);
                    if (futureMs.length) col4.appendChild(col4Ms);
                }

                // ── Col 5: Comments ──
                const projectId = `${proj.project_code}_${proj.engine_serial}`;
                const projectLabel = `${proj.engine_type} · SN ${proj.engine_serial}`;
                const col5 = document.createElement('div');
                col5.className = `${borderB} px-3 py-3 flex flex-col gap-2 cursor-pointer group`;
                col5.style.background = rowBg;
                col5.addEventListener('click', () => openCommentsModal(projectId, projectLabel));
                const comments = proj.comments || [];
                if (comments.length === 0) {
                    // blank — no placeholder
                } else {
                    comments.forEach((c, ci) => {
                        const item = document.createElement('div');
                        item.className = 'flex flex-col gap-0.5';
                        // Author initials + date line
                        const authorParts = c.author.split(' ');
                        const initials = authorParts.map(w => w[0] || '').join('').toUpperCase().slice(0, 2);
                        const dateFmt = c.timestamp ? c.timestamp.slice(5).replace('-', '.') : '';
                        const meta = document.createElement('div');
                        meta.className = 'flex items-center gap-1.5';
                        meta.innerHTML = `<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-slate-200 text-slate-600 text-xs font-bold flex-shrink-0">${initials}</span><span class="text-xs text-slate-400">${dateFmt}</span>`;
                        // Comment text — tooltip shows full text on hover
                        const txt = document.createElement('p');
                        txt.className = 'text-xs text-slate-600 leading-snug pl-6';
                        txt.style.cssText = 'display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden';
                        txt.textContent = c.text;
                        item.appendChild(meta);
                        item.appendChild(txt);
                        col5.appendChild(item);
                        if (ci < comments.length - 1) {
                            const divider = document.createElement('div');
                            divider.className = 'border-t border-slate-100';
                            col5.appendChild(divider);
                        }
                    });
                }
                // Subtle hover highlight to signal clickability
                col5.addEventListener('mouseenter', () => col5.style.background = idx === 0 ? '#DBEAFE' : '#F8FAFC');
                col5.addEventListener('mouseleave', () => col5.style.background = rowBg);

                grid.appendChild(col1);
                grid.appendChild(col2);
                grid.appendChild(col3);
                grid.appendChild(col4);
                grid.appendChild(col5);
            });
        });

        // ── Footer legend ──────────────────────────────────────────────────────
        const legend = document.createElement('div');
        legend.style.gridColumn = '1 / -1';
        legend.className = 'bg-slate-50 px-4 py-3 flex flex-wrap gap-5 text-xs text-slate-500 border-t border-slate-200';
        legend.innerHTML = `
            <div class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded bg-gray-100 border border-gray-300"></span>Abgeschlossen</div>
            <div class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded bg-blue-600 border border-blue-700"></span>Heute aktiv</div>
            <div class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded bg-blue-50 border border-blue-300"></span>Geplant (Meilenstein)</div>
            <div class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded" style="background:#FFF7ED;border:1px solid #F97316"></span>TEST</div>
            <div class="flex items-center gap-1.5"><span class="inline-block w-3 h-3 rounded" style="background:#FDF2F8;border:1px solid #EC4899"></span>RIG / DE-RIG</div>
        `;
        grid.appendChild(legend);

        wrapper.appendChild(grid);
        container.appendChild(wrapper);
    }
    // ── PROJEKTPLAN COMMENTS MODAL ───────────────────────────────────────────
    function openCommentsModal(projectId, projectName) {
        document.getElementById('ppCommentsTitle').textContent = projectName;
        document.getElementById('ppCommentsSubtitle').textContent = 'Projekt-Kommentare';
        const list = document.getElementById('ppCommentsList');
        list.innerHTML = '<p class="text-slate-400 italic text-sm">Lade Kommentare\u2026</p>';
        document.getElementById('ppCommentsOverlay').classList.remove('hidden');
        fetch(`/api/comments/${encodeURIComponent(projectId)}`)
            .then(r => r.ok ? r.json() : Promise.reject())
            .then(comments => {
                if (!comments.length) {
                    list.innerHTML = '<p class="text-slate-400 italic text-sm">Keine Kommentare vorhanden.</p>';
                    return;
                }
                list.innerHTML = '';
                comments.forEach(c => {
                    const authorParts = (c.author || '').split(' ');
                    const initials = authorParts.map(w => w[0] || '').join('').toUpperCase().slice(0, 2);
                    const ts = c.timestamp ? new Date(c.timestamp).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) : '';
                    const item = document.createElement('div');
                    item.className = 'flex gap-3';
                    const avatar = document.createElement('div');
                    avatar.className = 'flex-shrink-0 w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center text-xs font-bold text-slate-600';
                    avatar.textContent = initials;
                    const body = document.createElement('div');
                    body.className = 'flex-1 min-w-0';
                    const meta = document.createElement('div');
                    meta.className = 'flex items-baseline gap-2 mb-1';
                    meta.innerHTML = `<span class="text-sm font-semibold text-slate-700">${c.author || ''}</span><span class="text-xs text-slate-400">${ts}</span>`;
                    const txt = document.createElement('p');
                    txt.className = 'text-sm text-slate-600 bg-slate-50 border border-slate-100 rounded-lg px-3 py-2 whitespace-pre-wrap';
                    txt.textContent = c.comment || c.text || '';
                    body.appendChild(meta);
                    body.appendChild(txt);
                    item.appendChild(avatar);
                    item.appendChild(body);
                    list.appendChild(item);
                });
            })
            .catch(() => {
                list.innerHTML = '<p class="text-red-400 italic text-sm">Fehler beim Laden der Kommentare.</p>';
            });
    }
    function closePpComments() {
        document.getElementById('ppCommentsOverlay').classList.add('hidden');
    }
    // ── END PROJEKTPLAN ──────────────────────────────────────────────────────

    async function loadSkillMatrix() {
        const table = document.getElementById('skillMatrixTable');
        table.querySelector('tbody').innerHTML = '<tr><td class="p-4">Lade Daten...</td></tr>';
        
        try {
            const res = await fetch('/api/analytics/skill_matrix');
            const data = await res.json();
            renderMatrix(data);
        } catch(e) { console.error(e); }
    }
    
    function renderMatrix(data) {
        const theadRow = document.querySelector('#skillMatrixTable thead tr');
        const tbody = document.querySelector('#skillMatrixTable tbody');
        
        // Header bauen
        theadRow.innerHTML = '<th class="px-6 py-4 font-bold border-b border-r sticky left-0 bg-slate-50 z-10 w-48">Mitarbeiter</th>';
        data.engines.forEach(eng => {
            theadRow.innerHTML += `<th class="px-4 py-3 font-semibold border-b text-center min-w-[100px]">${eng}</th>`;
        });
        
        // Body bauen
        tbody.innerHTML = '';
        const today = new Date();
        
        data.matrix.forEach(user => {
            let rowHtml = `<td class="px-6 py-3 font-medium text-slate-800 border-r bg-white sticky left-0">${user.name}</td>`;
            
            data.engines.forEach(eng => {
                const skillData = user.skills[eng]; // Ist jetzt ein Objekt oder undefined
                
                let cellClass = "bg-slate-50"; 
                let text = "-";
                let clickAction = "";
                let cursorClass = "cursor-default";
    
                if (skillData) {
                    const lastDateIso = skillData.last_total;
                    const date = new Date(lastDateIso);
                    const diffDays = (today - date) / (1000 * 60 * 60 * 24);
                    
                    text = date.toLocaleDateString('de-DE', {month:'2-digit', year:'2-digit'});
                    
                    if (diffDays < 90) cellClass = "bg-green-100 text-green-800";
                    else if (diffDays < 180) cellClass = "bg-yellow-50 text-yellow-800";
                    else cellClass = "bg-red-100 text-red-800 font-bold";
                    
                    // Interaktion aktivieren
                    // Wir speichern die Task-Daten direkt im DOM-Element als JSON-String
                    const taskDataSafe = JSON.stringify(skillData.tasks).replace(/"/g, '&quot;');
                    clickAction = `openSkillDetails('${user.name}', '${eng}', ${taskDataSafe})`;
                    cursorClass = "cursor-pointer hover:brightness-90";
                }
                
                rowHtml += `<td class="px-4 py-3 text-center border-l border-slate-100 ${cellClass} ${cursorClass}" onclick="${clickAction}">${text}</td>`;
            });
            
            tbody.innerHTML += `<tr class="hover:brightness-95 transition">${rowHtml}</tr>`;
        });
    }
    function openSkillDetails(userName, engine, tasks) {
        document.getElementById('skillDetailTitle').textContent = engine;
        document.getElementById('skillDetailSubtitle').textContent = userName;
        
        const list = document.getElementById('skillDetailList');
        list.innerHTML = '';
        
        const today = new Date();
        
        // Sortieren: Neueste zuerst
        const sortedTasks = Object.entries(tasks).sort((a,b) => new Date(b[1]) - new Date(a[1]));
        
        sortedTasks.forEach(([taskName, dateIso]) => {
            const d = new Date(dateIso);
            const diff = Math.round((today - d) / (1000 * 60 * 60 * 24));
            const dateStr = d.toLocaleDateString('de-DE');
            
            let color = "bg-green-100 text-green-800";
            if(diff > 180) color = "bg-red-100 text-red-800";
            else if(diff > 90) color = "bg-yellow-100 text-yellow-800";
            
            list.innerHTML += `
                <div class="flex justify-between items-center p-2 rounded border border-slate-100">
                    <span class="font-medium text-sm text-slate-700">${taskName}</span>
                    <span class="text-xs px-2 py-0.5 rounded ${color}">${dateStr} (${diff}d)</span>
                </div>`;
        });
        
        document.getElementById('skillDetailModal').classList.remove('hidden');
    }

    // ── EFFICIENCY EDIT PANEL ───────────────────────────────────────────────
    function toggleEfficiencyEdit() {
        const panel = document.getElementById('efficiencyEditPanel');
        const btn = document.getElementById('efficiencyEditBtn');
        const isHidden = panel.classList.contains('hidden');
        if (isHidden) {
            panel.classList.remove('hidden');
            btn.innerHTML = '<span class="material-icons-outlined text-sm">close</span> Schließen';
            btn.classList.replace('text-slate-500', 'text-blue-600');
            // Immer mit aktuellem Monat starten
            efficiencyEditYear = new Date().getFullYear();
            efficiencyEditMonth = new Date().getMonth();
            buildEfficiencyEditTable();
            const targetInput = document.getElementById('efficiencyTargetInput');
            if (targetInput) targetInput.value = sfmEfficiencyTarget;
        } else {
            panel.classList.add('hidden');
            btn.innerHTML = '<span class="material-icons-outlined text-sm">edit</span> Bearbeiten';
            btn.classList.replace('text-blue-600', 'text-slate-500');
        }
    }

    function efficiencyNavMonth(direction) {
        const today = new Date();
        efficiencyEditMonth += direction;
        if (efficiencyEditMonth > 11) { efficiencyEditMonth = 0; efficiencyEditYear++; }
        if (efficiencyEditMonth < 0)  { efficiencyEditMonth = 11; efficiencyEditYear--; }
        buildEfficiencyEditTable();
    }

    function buildEfficiencyEditTable() {
        const tbody = document.getElementById('efficiencyEditTableBody');
        if (!tbody) return;
        tbody.innerHTML = '';

        const today = new Date();
        const todayIso = today.toISOString().split('T')[0];
        const isCurrentMonth = (efficiencyEditYear === today.getFullYear() && efficiencyEditMonth === today.getMonth());

        // Label aktualisieren
        const label = document.getElementById('efficiencyEditMonthLabel');
        const monthDate = new Date(efficiencyEditYear, efficiencyEditMonth, 1);
        if (label) label.textContent = monthDate.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });

        // "Weiter"-Button deaktivieren wenn aktueller Monat
        const nextBtn = document.getElementById('efficiencyNavNextBtn');
        if (nextBtn) nextBtn.disabled = isCurrentMonth;

        // Letzter Tag des angezeigten Monats (oder heute, falls aktueller Monat)
        const lastDay = isCurrentMonth ? today : new Date(efficiencyEditYear, efficiencyEditMonth + 1, 0);
        const monthStart = new Date(efficiencyEditYear, efficiencyEditMonth, 1);

        // Tage des Monats, neueste zuerst
        const daysInMonth = [];
        for (let d = new Date(lastDay); d >= monthStart; d.setDate(d.getDate() - 1)) {
            daysInMonth.push(new Date(d));
        }

        for (const d of daysInMonth) {
            const iso = d.toISOString().split('T')[0];
            const label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: '2-digit' });
            const stored = sfmEfficiencyData[iso] || {};
            const isToday = iso === todayIso;

            const tr = document.createElement('tr');
            tr.className = isToday ? 'bg-blue-50 font-semibold' : 'hover:bg-slate-50';
            tr.dataset.date = iso;

            const tdDate = document.createElement('td');
            tdDate.className = 'px-3 py-1.5 border border-slate-100 text-slate-700 whitespace-nowrap';
            tdDate.textContent = (isToday ? '★ ' : '') + label;
            tr.appendChild(tdDate);

            ['IGT', 'CFX', 'PWC', 'Gesamt'].forEach(key => {
                const td = document.createElement('td');
                td.className = 'px-1 py-1 border border-slate-100 text-center';
                const input = document.createElement('input');
                input.type = 'number';
                input.min = 0; input.max = 100; input.step = 0.1;
                input.value = stored[key] != null ? stored[key] : '';
                input.placeholder = '—';
                input.dataset.key = key;
                input.className = 'w-full text-xs text-center border border-slate-200 rounded px-1 py-1 bg-white text-slate-700 hover:border-blue-300 focus:border-blue-400 focus:ring-1 focus:ring-blue-300 focus:outline-none';
                input.addEventListener('blur', () => saveEfficiencyDay(iso));
                input.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); input.blur(); } });
                td.appendChild(input);
                tr.appendChild(td);
            });

            tbody.appendChild(tr);
        }
    }

    async function saveEfficiencyDay(iso) {
        const row = document.querySelector(`#efficiencyEditTableBody tr[data-date="${iso}"]`);
        if (!row) return;
        const payload = { date: iso };
        row.querySelectorAll('input[data-key]').forEach(inp => {
            const v = inp.value.trim();
            payload[inp.dataset.key] = v !== '' ? parseFloat(v) : null;
        });
        let saveOk = false;
        try {
            const res = await fetch('/api/sfm/efficiency/day', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            });
            saveOk = res.ok;
        } catch(e) { /* Netzwerkfehler still */ }

        if (saveOk) {
            sfmEfficiencyData[iso] = { IGT: payload.IGT, CFX: payload.CFX, PWC: payload.PWC, Gesamt: payload.Gesamt };
            renderEfficiencyChart();
            row.style.transition = 'background 0.3s';
            row.style.background = '#dcfce7';
            setTimeout(() => { row.style.background = ''; }, 800);
        } else {
            row.style.transition = 'background 0.3s';
            row.style.background = '#fee2e2';
            setTimeout(() => { row.style.background = ''; }, 1200);
        }
    }

    async function saveEfficiencyTarget() {
        const input = document.getElementById('efficiencyTargetInput');
        const val = parseFloat(input.value);
        if (isNaN(val) || val < 0 || val > 100) return;
        let saveOk = false;
        try {
            const res = await fetch('/api/sfm/efficiency/target', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ target: val })
            });
            saveOk = res.ok;
        } catch(e) { /* still */ }
        if (saveOk) {
            sfmEfficiencyTarget = val;
            renderEfficiencyChart();
        }
    }

    // ── PEOPLE GROUPS MODAL ─────────────────────────────────────────────────
    async function openPeopleGroupsModal() {
        const modal = document.getElementById('peopleGroupsModal');
        const checkboxContainer = document.getElementById('peopleGroupsCheckboxes');
        const saveBtn = document.getElementById('peopleGroupsSaveBtn');
        checkboxContainer.innerHTML = '<p class="text-xs text-slate-400 italic">Lade Gruppen...</p>';
        modal.classList.remove('hidden');

        try {
            const res = await fetch('/api/sfm/people_groups');
            const data = await res.json();
            const { available, selected } = data;

            checkboxContainer.innerHTML = '';
            if (!available || available.length === 0) {
                checkboxContainer.innerHTML = '<p class="text-xs text-slate-400 italic">Keine Gruppen gefunden.</p>';
                return;
            }

            available.forEach(group => {
                const isChecked = selected.includes(group);
                const label = document.createElement('label');
                label.className = 'flex items-center gap-2 cursor-pointer py-1';
                label.innerHTML = `
                    <input type="checkbox" value="${group}" ${isChecked ? 'checked' : ''} ${saveBtn ? '' : 'disabled'}
                        class="w-4 h-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500 peoplegroup-cb">
                    <span class="text-sm font-medium text-slate-700">${group}</span>`;
                checkboxContainer.appendChild(label);
            });
        } catch(e) {
            checkboxContainer.innerHTML = '<p class="text-xs text-red-500">Fehler beim Laden.</p>';
        }
    }

    async function savePeopleGroups() {
        const checked = [...document.querySelectorAll('.peoplegroup-cb:checked')].map(cb => cb.value);
        let saveOk = false;
        try {
            const res = await fetch('/api/sfm/people_groups', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ selected: checked })
            });
            if (res.ok) {
                saveOk = true;
            } else {
                alert('Fehler beim Speichern.');
            }
        } catch(e) {
            alert('Netzwerkfehler beim Speichern.');
        }
        if (saveOk) {
            sfmPeopleGroupsFilter = checked;
            closePeopleGroupsModal();
            updatePeopleCount();
        }
    }

    function closePeopleGroupsModal() {
        document.getElementById('peopleGroupsModal').classList.add('hidden');
    }

    function updatePeopleCount() {
        const todayIso = new Date().toISOString().split('T')[0];
        const yDate = new Date(); let db = 1;
        if (yDate.getDay() === 1) db = 3;
        if (yDate.getDay() === 0) db = 2;
        yDate.setDate(yDate.getDate() - db);
        const yIso = yDate.toISOString().split('T')[0];

        const filtered = (sfmPeopleGroupsFilter && sfmPeopleGroupsFilter.length > 0)
            ? allEmployees.filter(e => sfmPeopleGroupsFilter.includes((e.quali || '').trim()))
            : allEmployees;

        const tDate = new Date(todayIso);
        const yDateObj = new Date(yIso);
        const todayDow = tDate.getUTCDay();
        const yDow = yDateObj.getUTCDay();
        const todayIsWorkday = todayDow !== 0 && todayDow !== 6;
        const yIsWorkday = yDow !== 0 && yDow !== 6;

        let today = 0, yesterday = 0;
        filtered.forEach(emp => {
            let absentToday = false, absentYesterday = false;
            (emp.weeks || []).forEach(week => {
                (week.days || []).forEach(day => {
                    let d = day.date;
                    if (typeof d !== 'string') { try { d = new Date(day.date).toISOString().split('T')[0]; } catch(e){} }
                    if (day.ereignis && day.ereignis.trim()) {
                        if (d === todayIso) absentToday = true;
                        if (d === yIso) absentYesterday = true;
                    }
                });
            });
            if (todayIsWorkday && !absentToday) today++;
            if (yIsWorkday && !absentYesterday) yesterday++;
        });

        const countEl = document.getElementById('sfm-people-count');
        if (countEl) countEl.textContent = today;

        const totalEl = document.getElementById('sfm-people-total');
        const totalValEl = document.getElementById('sfm-people-total-val');
        if (totalEl && totalValEl) {
            const total = filtered.length;
            totalValEl.textContent = total;
            totalEl.style.display = total > 0 ? 'inline' : 'none';
        }

        const diffEl = document.getElementById('sfm-people-diff');
        if (diffEl) {
            const diff = today - yesterday;
            diffEl.classList.remove('hidden');
            if (diff > 0) {
                diffEl.className = 'flex items-center text-xs font-bold px-2 py-0.5 rounded-full bg-green-100 text-green-700';
                diffEl.innerHTML = `<span class="material-icons-outlined text-[14px] mr-0.5">trending_up</span> +${diff}`;
            } else if (diff < 0) {
                diffEl.className = 'flex items-center text-xs font-bold px-2 py-0.5 rounded-full bg-red-100 text-red-700';
                diffEl.innerHTML = `<span class="material-icons-outlined text-[14px] mr-0.5">trending_down</span> ${diff}`;
            } else {
                diffEl.className = 'flex items-center text-xs font-bold px-2 py-0.5 rounded-full bg-slate-100 text-slate-500';
                diffEl.innerHTML = `<span class="material-icons-outlined text-[14px] mr-0.5">remove</span> +/- 0`;
            }
        }
    }

</script>

<!-- PEOPLE GROUPS MODAL -->
<div id="peopleGroupsModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
    <div class="bg-white rounded-xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden">
        <div class="flex items-center justify-between px-5 py-4 border-b border-slate-100 bg-slate-50">
            <h3 class="font-bold text-slate-800 flex items-center gap-2 text-sm">
                <span class="material-icons-outlined text-blue-500 text-base">group_work</span>
                Gruppen in "People (Heute)"
            </h3>
            <button onclick="closePeopleGroupsModal()" class="text-slate-400 hover:text-slate-600">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <div class="px-5 py-4">
            <p class="text-xs text-slate-500 mb-3">Wähle welche Mitarbeitergruppen in der Anwesenheitszählung berücksichtigt werden. Keine Auswahl = alle Gruppen.</p>
            <div id="peopleGroupsCheckboxes" class="flex flex-col gap-0.5 max-h-60 overflow-y-auto"></div>
        </div>
        <div class="px-5 py-3 border-t border-slate-100 bg-slate-50 flex justify-end gap-2">
            <button onclick="closePeopleGroupsModal()" class="px-4 py-2 text-sm text-slate-600 bg-white border border-slate-200 rounded-lg hover:bg-slate-50">
                Schließen
            </button>
            {% if session.get('is_admin') %}
            <button id="peopleGroupsSaveBtn" onclick="savePeopleGroups()" class="px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded-lg hover:bg-blue-700">
                Speichern
            </button>
            {% endif %}
        </div>
    </div>
</div>
</div>
</body>
</html>
"""

EDITABLE_TIMELINE_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Zeitleiste Bearbeiten</title>
    <link crossorigin="" href="https://fonts.gstatic.com/" rel="preconnect"/>
    <link as="style" href="https://fonts.googleapis.com/css2?display=swap&family=Inter%3Awght%40400%3B500%3B600%3B700&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900" onload="this.rel='stylesheet'" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <style>
        body {
            font-family: Inter, 'Noto Sans', sans-serif;
            background-color: #F5F6F8;
            -webkit-user-select: none; /* Safari */
            -moz-user-select: none;    /* Firefox */
            -ms-user-select: none;     /* IE10+/Edge */
            user-select: none;         /* Standard */
        }
        body.is-resizing, body.is-dragging, body.is-row-dragging {
            cursor: grabbing !important;
        }
        body.is-resizing {
            cursor: col-resize !important;
        }
        body.is-row-dragging { cursor: move !important; }

        .table-container {
            overflow: auto;
            max-height: 75vh;
            border: 1px solid #e5e7eb;
            border-radius: 0.5rem;
        }

        .unified-table {
            border-collapse: separate;
            border-spacing: 0;
        }
        .unified-table th, .unified-table td {
            padding: 0;
            vertical-align: middle;
            white-space: nowrap;
        }
        .unified-table tr { height: 75px; }
        .unified-table tbody td {
            border-bottom: 1px solid #e5e7eb;
            border-right: 1px solid #e5e7eb;
            min-width: 45px; width: 45px; max-width: 45px;
            position: relative;
        }
        .unified-table tbody tr:last-child td { border-bottom: 0; }
        .unified-table tfoot td { border-right: 1px solid #e5e7eb; }
        .unified-table td:last-child, .unified-table th:last-child { border-right: 0; }

        .unified-table thead th {
            position: sticky;
            background-color: #f9fafb;
        }
        .header-row-dates th { top: 0; z-index: 21; }
        .header-row-shifts th { top: 35px; z-index: 20; }

        .unified-table tfoot td {
            position: sticky;
            bottom: 0;
            z-index: 20;
            background-color: #ffffff;
            border-top: 2px solid #d1d5db;
            transition: box-shadow 0.2s ease-in-out;
        }
        .table-container.is-scrollable-down .unified-table tfoot td {
            box-shadow: inset 0 10px 8px -8px rgba(0, 0, 0, 0.12);
        }

        .unified-table .sticky-col {
            position: sticky;
            left: 0;
            z-index: 10;
            background-color: #ffffff;
            border-right: 1px solid #d1d5db;
            width: 280px; min-width: 280px; max-width: 280px;
            padding-left: 1rem;
            padding-right: 1rem;
        }
        .unified-table tbody tr:hover .sticky-col {
            background-color: #f3f4f6;
        }
        .unified-table thead .sticky-col { background-color: #f9fafb; }
        .unified-table thead th.sticky-col, .unified-table tfoot td.sticky-col { z-index: 30; }

        .project-name-cell { font-size: 0.875rem; color: #334155; text-align: left; cursor: grab; }
        .header-cell-project { text-align: left; font-size: 0.875rem; font-weight: 600; color: #0f172a; }
        .header-cell-date { padding: 0 0.25rem; font-size: 0.75rem; font-weight: 500; color: #475569; text-align: center; height: 35px; }
        .header-cell-date > div { padding-top: 0.375rem; padding-bottom: 0.375rem; border-bottom: 1px solid #e5e7eb; }
        .header-cell-shift { padding: 0.25rem; font-size: 0.75rem; font-weight: 500; color: #64748b; text-align: center; }

        .weekend-bg { background-color: #f8fafc; }
        .weekend-header { background-color: #f1f5f9 !important; }
        .holiday-bg { background-color: #eff6ff; }
        .holiday-header { background-color: #dbeafe !important; }
        .current-timeslot-highlight { background-color: rgba(239, 68, 68, 0.1) !important; }

        .task-segment-container { width: 100%; height: 100%; display: flex; align-items: center; font-size: 0.75rem; color: white; overflow: hidden; cursor: pointer; }
        .task-segment-container.inactive { cursor: not-allowed; }
        .task-segment-content {
            flex-grow: 1;
            display: flex;
            flex-direction: column; /* WICHTIG: Elemente untereinander stapeln */
            justify-content: center; /* Zentriert den Inhalt vertikal */
            align-items: center;
            padding: 2px 1px;
            overflow: hidden;
            white-space: nowrap;
            height: 100%;
            text-align: center;
            gap: 4px; /* Kleiner Abstand zwischen Titel, MA und Zelle */
        }
        .task-name-display { font-weight: 600; font-size: 0.7rem; line-height: 1.1; margin-bottom: 3px; width: 100%; overflow: hidden; text-overflow: ellipsis; }
        .task-bar { width: 100%; height: 100%; position: relative; }
        .task-bar.is-dragging-segment { opacity: 0.5; }

        .resize-handle { position: absolute; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 10; pointer-events: auto; }
        .resize-handle.inactive { cursor: not-allowed; }
        .resize-handle-left { left: -4px; }
        .resize-handle-right { right: -4px; background-color: rgba(59, 130, 246, 0.5); border-top-right-radius: 3px; border-bottom-right-radius: 3px; transition: background-color 0.2s ease-in-out; }
        .resize-handle-right:hover { background-color: rgba(37, 99, 235, 0.8); }

        .drop-target-highlight { background-color: #bae6fd !important; }
        .row-drop-target-highlight { background-color: #dbeafe !important; }

        .connector-line-container { width: 100%; height: 100%; display: flex; align-items: center; cursor: pointer; }
        .connector-line { width: 100%; height: 2px; background-color: #d1d5db; transition: background-color 0.2s ease-in-out; }
        .connector-line-container:hover .connector-line { background-color: #9ca3af; }

        .toggle-active-btn, .reset-project-btn { 
            margin-left: 0.25rem; padding: 0.25rem 0.625rem; font-size: 0.75rem; line-height: 1rem;
            font-weight: 600; border-radius: 0.375rem; color: white;
            box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
            transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
            transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
            transition-duration: 150ms;
        }
        /* ### NEU: Button-Stile ### */
        .add-new-block-btn {
            margin-left: 0.25rem; padding: 0.375rem; border-radius: 0.375rem; color: #3b82f6; background-color: #eff6ff;
            box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
        }
        .add-new-block-btn:hover { background-color: #dbeafe; }
        .add-new-block-btn .material-icons-outlined { font-size: 14px; vertical-align: middle; }

        .open-comments-btn {
            position: relative;
            margin-left: 0.25rem; padding: 0.375rem; font-size: 0.75rem; line-height: 1rem;
            border-radius: 0.375rem; color: #64748b; background-color: #f1f5f9;
            box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
            transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
            transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
            transition-duration: 150ms;
        }
        .open-comments-btn:hover { background-color: #e2e8f0; }
        .open-comments-btn .material-icons-outlined { font-size: 14px; vertical-align: middle; }
        .delete-project-btn {
            margin-left: 0.25rem; padding: 0.25rem; font-size: 0.75rem; line-height: 1rem;
            border-radius: 0.375rem;
            box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
            transition-property: background-color;
            transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
            transition-duration: 150ms;
        }
        .comment-count-badge {
            position: absolute;
            top: -5px;
            right: -5px;
            background-color: #3b82f6; /* blue-600 */
            color: white;
            border-radius: 9999px;
            width: 16px;
            height: 16px;
            font-size: 10px;
            font-weight: 600;
            display: flex;
            align-items: center;
            justify-content: center;
            line-height: 1;
            border: 1px solid white;
        }

        .milestone-marker { position: absolute; left: 0; bottom: -9px; width: 18px; height: 18px; background-color: #FBBF24; border: 1px solid #B45309; transform: translateX(-50%) rotate(45deg); display: flex; align-items: center; justify-content: center; z-index: 15; pointer-events: all; cursor: help; }
        .milestone-marker span { transform: rotate(-45deg); font-size: 8px; font-weight: 700; color: #422006; }

        .animate-spin { animation: spin 1s linear infinite; }
        @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

        .employee-avatars-container {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 2px;
            /* margin-top entfernt, da wir jetzt 'gap' im Eltern-Container nutzen */
            height: 16px; 
            width: 100%; /* Volle Breite nutzen */
        }
        .employee-avatar { 
            position: relative; width: 16px; height: 16px; border-radius: 50%;
            display: flex; justify-content: center; align-items: center; 
            font-size: 0.55rem; font-weight: 700; line-height: 1; 
            color: white; border: 1px solid rgba(0,0,0,0.2);
        }
        .test-cell-badge {
            position: relative;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            height: 16px;
            padding: 0 6px;
            border-radius: 6px; /* Eckig mit leicht runden Ecken (Unterschied zu MA) */
            font-size: 0.6rem;
            font-weight: 700;
            line-height: 1;
            color: white;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            max-width: 80%; /* Damit es nicht über den Block ragt */
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .manual-assignment-dot { position: absolute; top: -1px; right: -1px; width: 5px; height: 5px; background-color: #ef4444; border-radius: 50%; border: 1px solid white; }

        .avatars-in-cell-container {
            display: flex;
            flex-wrap: wrap;
            justify-content: flex-start;
            align-items: flex-start;
            gap: 2px;
            padding: 2px;
            min-height: 36px;
        }
        .available-employee-avatar {
            width: 16px;
            height: 16px;
            border-radius: 9999px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.55rem;
            font-weight: 700;
            line-height: 1;
            position: relative;
        }
        .footer-cell-clickable { cursor: pointer; }
        .remaining-count {
            font-size: 0.6rem;
            font-weight: 600;
            color: #64748b;
            text-align: center;
            margin-top: 2px;
            line-height: 1;
        }

        .modal-backdrop { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 40; display: none; }
        .modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 50; display: none; }
        .modal-content { background-color: white; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); width: 90vw; max-width: 800px; }
        .assigned-employee-tag { @apply inline-flex items-center gap-x-1.5 rounded-md px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800; }
        .remove-employee-btn { @apply ml-1.5 inline-flex h-4 w-4 items-center justify-center rounded-full text-blue-600 hover:bg-blue-200 hover:text-blue-700; }

        .comments-scrollbar::-webkit-scrollbar { width: 8px; }
        .comments-scrollbar::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; }
        .comments-scrollbar::-webkit-scrollbar-thumb { background: #c4c4c4; border-radius: 10px; }
        .comments-scrollbar::-webkit-scrollbar-thumb:hover { background: #a1a1a1; }

        .comment-container { position: relative; }
        .comment-actions {
            position: absolute;
            top: 0.5rem;
            right: 0.5rem;
            display: none;
        }
        .comment-container:hover .comment-actions {
            display: block;
        }
        .comment-actions-menu {
            position: absolute;
            right: 0;
            top: 1.5rem;
            background-color: white;
            border: 1px solid #e5e7eb;
            border-radius: 0.375rem;
            box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
            z-index: 10;
        }
        .comment-actions-menu button {
            display: block;
            width: 100%;
            text-align: left;
            padding: 0.5rem 1rem;
            font-size: 0.875rem;
            line-height: 1.25rem;
        }
        .comment-actions-menu button:hover {
            background-color: #f3f4f6;
        }
                #sidebar {
            transition: width 0.3s ease-in-out;
        }
        #sidebar.is-collapsed {
            width: 5rem; /* 80px */
        }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info {
            display: none;
        }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile {
            justify-content: center;
        }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined {
            margin-right: 0;
        }
        .sidebar-label {
            transition: opacity 0.2s ease-in-out;
        }
        .sidebar-separator {
            display: none;
        }
        #sidebar.is-collapsed .sidebar-separator {
            display: block;
            height: 1px;
            background-color: #e5e7eb;
            margin: 0.5rem 0.75rem;
        }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        .task-bar.selected .task-segment-container {
            outline: 2px solid #ef4444;
            box-shadow: 0 0 0 2px #ef4444; /* Blauer Rahmen außen */
            z-index: 20;
        }

        /* Der Auswahlrahmen (Rubberband) */
        #selection-marquee {
            position: fixed;
            border: 1px solid #3b82f6;
            background-color: rgba(59, 130, 246, 0.2);
            z-index: 9999;
            pointer-events: none; /* Klicks gehen durch */
            display: none;
        }
    </style>
</head>
<body class="bg-white"> <!-- Ändere Hintergrund, falls nötig -->
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}
    <main class="flex-grow bg-neutral-50 px-4 sm:px-6 lg:px-8 py-8">
        <div class="bg-white p-4 sm:p-6 rounded-lg shadow-lg">
            <div class="flex flex-col md:flex-row justify-between items-center mb-6 gap-4">
                <div><h1 class="text-2xl font-bold tracking-tight text-slate-900">{{ timeline_name }}</h1><p class="text-sm text-slate-500 mt-1">Projekte und Ressourcen planen. Änderungen werden erst nach Klick auf "Speichern" übernommen.</p></div>
                <div class="flex items-center space-x-2">
                    <div class="relative">
                        <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" style="font-size: 20px;">search</span>
                        <input id="projectSearchInput" class="pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-48 text-sm" placeholder="Projekte suchen..." type="text"/>
                    </div>
                    <button id="prevWeekBtnEt" class="inline-flex items-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 disabled:opacity-50">
                        <span class="material-icons-outlined text-base -ml-0.5">arrow_back_ios</span>Vorherige
                    </button>
                    <button id="currentWeekBtnEt" class="inline-flex items-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 disabled:opacity-50">
                        <span class="material-icons-outlined text-base -ml-0.5">event_repeat</span>Aktuell
                    </button>
                    <button id="nextWeekBtnEt" class="inline-flex items-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 disabled:opacity-50">
                        Nächste<span class="material-icons-outlined text-base -mr-0.5">arrow_forward_ios</span>
                    </button>
                    <button id="createProjectBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600">
                        <span class="material-icons-outlined text-base -ml-0.5">add_circle_outline</span>New
                    </button>
                    <button id="sortProjectsBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
                        <span class="material-icons-outlined text-base -ml-0.5">sort</span>Sort
                    </button>
                    <button id="openSapUploadModalBtn" class="hidden inline-flex items-center gap-x-1.5 rounded-md bg-purple-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-purple-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-purple-600 disabled:opacity-50">
                        <span class="material-icons-outlined text-base -ml-0.5">cloud_upload</span>SAP
                    </button>
                    <button id="sapUpdateBtn" class="hidden inline-flex items-center gap-x-1.5 rounded-md bg-teal-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600 disabled:opacity-50">
                        <span class="material-icons-outlined text-base -ml-0.5">sync</span>SAP
                    </button>
                    <button id="saveTimelineChangesBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-50">
                        <span class="material-icons-outlined text-base -ml-0.5">save</span>Save
                    </button>
                    <button id="tempGroupsBtn" class="w-9 h-9 bg-slate-100 rounded-full flex items-center justify-center cursor-pointer hover:bg-slate-200 ml-2" title="Team & Aushilfen verwalten">
                        <span class="material-icons-outlined text-slate-600">group_add</span>
                    </button>
                    <button id="settingsBtn" class="w-9 h-9 bg-slate-100 rounded-full flex items-center justify-center cursor-pointer hover:bg-slate-200" title="Einstellungen">
                        <span class="material-icons-outlined text-slate-600">settings</span>
                    </button>
                </div>
            </div>

            <div class="table-container" id="unifiedTableContainer">
                <table class="min-w-full unified-table" id="unifiedTable">
                    <thead id="unifiedHeader"></thead>
                    <tbody id="unifiedBody" class="divide-y divide-slate-200 bg-white"></tbody>
                    <tfoot id="unifiedFooter"></tfoot>
                </table>
            </div>
        </div>
    </main>
</div>
<div id="selection-marquee"></div>
<div id="createProjectModal" class="modal">
    <div class="modal-content max-w-2xl">
        <div class="p-6">
            <div class="flex items-start justify-between">
                <div>
                    <h3 class="text-lg font-semibold leading-6 text-gray-900">Neues Projekt manuell erstellen</h3>
                    <p class="mt-1 text-sm text-gray-500">Geben Sie die Projektdaten ein. Events werden automatisch ab dem Startdatum generiert.</p>
                </div>
                <button id="closeCreateProjectModal" type="button" class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
            <form id="createProjectForm" class="mt-6 space-y-4">
                <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                    <div>
                        <label for="newProjectName" class="block text-sm font-medium text-gray-700">Projektname*</label>
                        <input type="text" id="newProjectName" class="form-input-modal w-full mt-1" required>
                    </div>
                    <div>
                        <label for="newEngineType" class="block text-sm font-medium text-gray-700">Triebwerkstyp*</label>
                        <input type="text" id="newEngineType" class="form-input-modal w-full mt-1" required>
                    </div>
                </div>
                <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                    <div>
                        <label for="newProjectCode" class="block text-sm font-medium text-gray-700">Projekt-Code*</label>
                        <input type="text" id="newProjectCode" class="form-input-modal w-full mt-1" required>
                    </div>
                    <div>
                        <label for="newEngineSerial" class="block text-sm font-medium text-gray-700">Engine Serial*</label>
                        <input type="text" id="newEngineSerial" class="form-input-modal w-full mt-1" required>
                    </div>
                </div>
                 <div>
                    <label for="newCustomer" class="block text-sm font-medium text-gray-700">Kunde</label>
                    <input type="text" id="newCustomer" class="form-input-modal w-full mt-1">
                </div>
                <div>
                    <label for="newEventsStartDate" class="block text-sm font-medium text-gray-700">Startdatum der Events*</label>
                    <input type="date" id="newEventsStartDate" class="form-input-modal w-full mt-1" required>
                </div>
            </form>
        </div>
        <div class="bg-gray-50 px-6 py-4 flex justify-end">
            <button id="submitNewProjectBtn" type="button" class="inline-flex justify-center rounded-md bg-green-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-700">
                Projekt erstellen & Speichern
            </button>
        </div>
    </div>
</div>

<div id="assignmentModalBackdrop" class="modal-backdrop"></div>
<div id="assignmentModal" class="modal">
    <div class="modal-content">
        <div class="p-6">
            <div class="flex items-start justify-between">
                <div>
                    <h3 class="text-lg font-semibold leading-6 text-gray-900" id="modalTitle">Mitarbeiter zuweisen</h3>
                    <p class="mt-1 text-sm text-gray-500" id="modalSubtitle">Slot-Details</p>
                </div>
                <div class="flex items-center space-x-2">
                    <button id="deleteEventBtn" type="button" class="rounded-md bg-red-50 text-red-500 hover:bg-red-100 hover:text-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 p-1.5" title="Event löschen">
                        <span class="material-icons-outlined">delete_outline</span>
                    </button>
                    <button id="closeAssignmentModal" type="button" class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
                        <span class="material-icons-outlined">close</span>
                    </button>
                </div>
            </div>
            <div class="mt-5">
                <h4 class="text-sm font-medium text-gray-700 mb-2">Zugewiesene Mitarbeiter</h4>
                <div id="assignedEmployeesContainer" class="space-x-2 space-y-2">
                    <p class="text-xs text-gray-400 italic">Keine Mitarbeiter zugewiesen.</p>
                </div>
            </div>
            <div class="mt-4">
                <label for="availableEmployeesSelect" class="block text-sm font-medium leading-6 text-gray-900">Verfügbare Mitarbeiter hinzufügen</label>
                <div class="mt-2 flex rounded-md shadow-sm">
                    <select id="availableEmployeesSelect" class="block w-full flex-1 rounded-none rounded-l-md border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"></select>
                    <button id="addEmployeeBtn" type="button" class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
                        <span class="material-icons-outlined text-base -ml-0.5">add</span> Hinzufügen
                    </button>
                </div>
            </div>
            <div class="mt-5 hidden border-t border-gray-100 pt-5" id="hardwareKitSection">
                <h4 class="text-sm font-medium text-gray-700 mb-2">Hardware-Kit</h4>
                <div id="assignedKitDisplay" class="space-x-2 space-y-2"></div>
            </div>
            <div class="mt-4 hidden" id="hardwareKitAddSection">
                <label class="block text-sm font-medium leading-6 text-gray-900">Verfügbares Hardware-Kit setzen</label>
                <div class="mt-2 flex rounded-md shadow-sm">
                    <select id="hardwareKitSelect" class="block w-full flex-1 rounded-none rounded-l-md border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 sm:text-sm sm:leading-6">
                        <option value="">-- Auto --</option>
                    </select>
                    <button id="saveKitBtn" type="button" class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
                        <span class="material-icons-outlined text-base -ml-0.5">check</span> Setzen
                    </button>
                </div>
            </div>
        </div>
        <div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
            <button id="saveAssignmentsBtn" type="button" class="mt-3 sm:mt-0 sm:ml-3 inline-flex w-full justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 sm:w-auto">Speichern</button>
            <button id="deleteAssignmentsBtn" type="button" class="mt-3 sm:mt-0 sm:ml-3 hidden inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:w-auto">Manuelle Zuweisung löschen</button>
            <button id="cancelAssignmentsBtn" type="button" class="mt-3 sm:mt-0 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:w-auto">Abbrechen</button>
        </div>
    </div>
</div>

<div id="blockerModal" class="modal">
    <div class="modal-content p-6">
        <div class="flex justify-between items-center mb-4">
            <h3 class="text-lg font-semibold leading-6 text-gray-900" id="blockerModalTitle">Manuelle Blockaden</h3>
            <!-- Schließen-Kreuz oben rechts -->
            <button onclick="document.getElementById('blockerModal').style.display='none'; document.getElementById('assignmentModalBackdrop').style.display='none';" class="text-gray-400 hover:text-gray-500">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>

        <!-- Liste mit Checkboxen -->
        <div id="blockerModalContent" class="mt-2 space-y-2 text-sm max-h-96 overflow-y-auto"></div>

        <!-- Footer mit Buttons -->
        <div class="mt-6 pt-4 border-t border-gray-100 flex justify-between items-center">
            <label class="flex items-center gap-2 text-sm text-gray-600 cursor-pointer">
                <input type="checkbox" id="selectAllBlockers" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
                Alle auswählen
            </label>
            <div class="flex gap-3">
                <button id="closeBlockerModal" class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Schließen</button>
                <button id="deleteSelectedBlockersBtn" class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 disabled:opacity-50 disabled:cursor-not-allowed">
                    Ausgewählte löschen
                </button>
            </div>
        </div>
    </div>
</div>

<div id="settingsModal" class="modal">
    <div class="modal-content max-w-4xl w-full flex flex-col max-h-[90vh] bg-white rounded-lg shadow-xl overflow-hidden">
        <!-- Fester Modal-Header -->
        <div class="p-6 border-b border-gray-200 flex-shrink-0">
            <div class="flex items-start justify-between">
                <h3 class="text-lg font-semibold leading-6 text-gray-900">Einstellungen</h3>
                <button id="closeSettingsModalBtn" type="button" class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
        </div>

        <!-- Scrollbarer Modal-Body -->
        <div class="mt-5 p-6 flex-grow overflow-y-auto">
            <!-- Der Inhalt (Mitarbeitergruppen, Testzellen und Regeln) wird hier per JavaScript eingefügt und ist nun scrollbar -->
        </div>

        <!-- Fester Modal-Footer -->
        <div class="p-6 bg-gray-50 border-t border-gray-200 text-right flex-shrink-0">
            <button id="saveSettingsBtn" type="button" class="inline-flex justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">Anwenden</button>
        </div>
    </div>
</div>

<div class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center z-50 hidden" id="commentsOverlay">
    <div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] flex flex-col" id="commentsModal">
        <div class="flex items-center justify-between p-4 border-b border-gray-200">
            <div class="flex items-center">
                <span class="material-icons-outlined text-teal-600 mr-2">forum</span>
                <h2 class="text-xl font-semibold text-gray-800" id="commentsModalTitle">Kommentare für Projekt</h2>
            </div>
            <button class="text-gray-500 hover:text-gray-700" id="closeCommentsButton">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <div class="p-6 space-y-6 overflow-y-auto comments-scrollbar flex-grow" id="commentsListContainer">
            <p class="text-center text-gray-500">Lade Kommentare...</p>
        </div>
        <div class="p-4 border-t border-gray-200 bg-gray-50">
            <div class="flex items-start space-x-3">
                <div class="flex-grow">
                    <textarea class="w-full p-2 border border-gray-300 rounded-md focus:ring-teal-500 focus:border-teal-500 resize-none" id="newCommentTextarea" placeholder="Neuen Kommentar hinzufügen..." rows="3"></textarea>
                    <div class="mt-2 flex justify-end">
                        <button id="postCommentBtn" class="bg-teal-600 hover:bg-teal-700 text-white font-semibold py-1.5 px-4 rounded-md text-sm flex items-center disabled:opacity-50 disabled:cursor-not-allowed">
                            <span class="material-icons-outlined text-base mr-1">send</span> Posten
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- ### NEU: ANFANG - Modal für manuellen SAP-Upload ### -->
<div id="manualSapUploadModal" class="modal">
    <div class="modal-content max-w-lg">
        <div class="p-6">
            <div class="flex items-start justify-between">
                <div>
                    <h3 class="text-lg font-semibold leading-6 text-gray-900">Manueller SAP-Daten-Upload</h3>
                    <p class="mt-1 text-sm text-gray-500">Ziehen Sie die `ZLSGS_Dataexport.csv` hierher oder wählen Sie sie aus.</p>
                </div>
                <button id="closeManualSapUploadModal" type="button" class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
            <div id="manual-sap-drop-zone" class="mt-6 flex justify-center items-center w-full px-6 py-10 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-colors duration-200">
                <div class="text-center">
                    <span class="material-icons-outlined text-4xl text-gray-400">cloud_upload</span>
                    <p class="mt-2 text-sm text-gray-600">
                        <span class="font-semibold">SAP-CSV hierher ziehen</span> oder klicken zum Auswählen
                    </p>
                    <p class="text-xs text-gray-500 mt-1">Nur `ZLSGS_Dataexport.csv` mit aktuellem Format.</p>
                </div>
                <input type="file" id="manual-sap-file-input" class="hidden" accept=".csv">
            </div>
            <div id="manual-sap-upload-status" class="mt-4 text-sm text-center"></div>
        </div>
        <div class="bg-gray-50 px-6 py-4 flex justify-end">
            <button id="cancelManualSapUploadBtn" type="button" class="inline-flex justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Abbrechen</button>
        </div>
    </div>
</div>
<!-- Modal für temporäre Gruppen -->
<div id="tempGroupsModal" class="modal">
    <div class="modal-content max-w-sm">
        <div class="p-6 border-b border-gray-200">
            <div class="flex items-start justify-between">
                <h3 class="text-lg font-semibold leading-6 text-gray-900">Team-Zusammenstellung</h3>
                <button id="closeTempGroupsModalBtn" type="button" class="text-gray-400 hover:text-gray-500">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
            <p class="text-xs text-gray-500 mt-1">Wähle Gruppen aus, die temporär für diese Planung verfügbar sein sollen.</p>
        </div>
        <div class="p-6 max-h-64 overflow-y-auto">
            <div id="tempGroupsList" class="space-y-3">
                <!-- Checkboxen werden hier generiert -->
            </div>
        </div>
        <div class="p-6 bg-gray-50 border-t border-gray-200 text-right">
            <button id="applyTempGroupsBtn" type="button" class="inline-flex justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">Anwenden</button>
        </div>
    </div>
</div>

<script>
    const timelineId = {{ timeline_id|tojson }};

document.addEventListener('DOMContentLoaded', () => {

    // --- NEU: LOGIK FÜR DIE EINKLAPPBARE SIDEBAR ---
    const sidebar = document.getElementById('sidebar');
    const sidebarToggleBtn = document.getElementById('sidebar-toggle');

    if (sidebar && sidebarToggleBtn) {
        const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');

        const applyState = (isCollapsed) => {
            if (isCollapsed) {
                sidebar.classList.add('is-collapsed');
                toggleBtnIcon.textContent = 'menu';
            } else {
                sidebar.classList.remove('is-collapsed');
                toggleBtnIcon.textContent = 'chevron_left';
            }
        };

        sidebarToggleBtn.addEventListener('click', () => {
            const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
            localStorage.setItem('sidebarCollapsed', isNowCollapsed);
            applyState(isNowCollapsed);
        });

        // Initialen Zustand aus dem Local Storage laden
        const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
        applyState(savedState);
    }
    // --- ENDE DES NEUEN BLOCKS ---


    // --- BESTEHENDE LOGIK FÜR DIE EDITABLE TIMELINE ---
    const currentUsername = "{{ session.get('username_display') }}";
    const isAdmin = {{ 'true' if session.get('is_admin') else 'false' }};

    // DOM-Elemente
    const unifiedBody = document.getElementById('unifiedBody');
    const unifiedFooter = document.getElementById('unifiedFooter');
    const prevWeekBtn = document.getElementById('prevWeekBtnEt');
    const currentWeekBtn = document.getElementById('currentWeekBtnEt');
    const nextWeekBtn = document.getElementById('nextWeekBtnEt');
    const saveBtn = document.getElementById('saveTimelineChangesBtn');
    const sapUpdateBtn = document.getElementById('sapUpdateBtn');
    const searchInput = document.getElementById('projectSearchInput');
    const settingsBtn = document.getElementById('settingsBtn');
    const sortBtn = document.getElementById('sortProjectsBtn');
    const assignmentModal = document.getElementById('assignmentModal');
    const assignmentModalBackdrop = document.getElementById('assignmentModalBackdrop');
    const modalTitle = document.getElementById('modalTitle');
    const modalSubtitle = document.getElementById('modalSubtitle');
    const assignedEmployeesContainer = document.getElementById('assignedEmployeesContainer');
    const availableEmployeesSelect = document.getElementById('availableEmployeesSelect');
    const addEmployeeBtn = document.getElementById('addEmployeeBtn');
    const saveAssignmentsBtn = document.getElementById('saveAssignmentsBtn');
    const cancelAssignmentsBtn = document.getElementById('cancelAssignmentsBtn');
    const closeAssignmentModalBtn = document.getElementById('closeAssignmentModal');
    const deleteAssignmentsBtn = document.getElementById('deleteAssignmentsBtn');
    const deleteEventBtn = document.getElementById('deleteEventBtn');
    const blockerModal = document.getElementById('blockerModal');
    const blockerModalTitle = document.getElementById('blockerModalTitle');
    const blockerModalContent = document.getElementById('blockerModalContent');
    const closeBlockerModalBtn = document.getElementById('closeBlockerModal');
    const settingsModal = document.getElementById('settingsModal');
    const closeSettingsModalBtn = document.getElementById('closeSettingsModalBtn');
    const employeeGroupFilterSelect = document.getElementById('employeeGroupFilterSelect');
    const saveSettingsBtn = document.getElementById('saveSettingsBtn');
    const commentsOverlay = document.getElementById('commentsOverlay');
    const commentsModal = document.getElementById('commentsModal');
    const closeCommentsButton = document.getElementById('closeCommentsButton');
    const commentsModalTitle = document.getElementById('commentsModalTitle');
    const commentsListContainer = document.getElementById('commentsListContainer');
    const newCommentTextarea = document.getElementById('newCommentTextarea');
    const postCommentBtn = document.getElementById('postCommentBtn');
    let activeProjectForComments = null;

    const configModal = document.getElementById('settingsModal');
    const openConfigBtn = document.getElementById('settingsBtn');
    const closeConfigBtn = document.getElementById('closeSettingsModalBtn');
    const saveConfigBtn = document.getElementById('saveSettingsBtn');
    const configModalBody = configModal.querySelector('.overflow-y-auto');

    let assignmentRules = [];
    let ruleOptions = {};
    let allAvailableSkills = [];
    
    let selectedTaskIds = new Set(); // Speichert IDs der markierten Tasks
    let isSelecting = false;
    let selectionStart = { x: 0, y: 0 };
    const selectionMarquee = document.getElementById('selection-marquee');

    let activeDragData = null;
    let isResizing = false;
    let resizingTask = null;
    let currentModalData = null;
    let isDraggingFlag = false;
    let currentEmployeeGroupFilter = []; 
    let currentTestCellFilter = [];
    let engineEventMapping = {};
    let freeDaysSet = new Set();

    let masterApiDataCache = null;
    let masterTaskData = {};
    let masterProjectDetails = {};
    let masterProjectOrder = [];
    let filteredProjectOrder = [];
    let masterTimelineStartDate = null;
    let masterTotalColumns = 0;
    let masterFooterData = {}; 
    let masterQualStatuses = {};

    let allEmployees = [];
    let allQualifications = [];
    let allLocations = [];
    let manualAssignments = new Map();
    let employeeQualificationsMap = new Map();
    let employeeAvailabilityMap = new Map();
    let employeeColorMap = new Map();

    let currentWeekOffset = 3;
    const WEEKS_IN_PAST = 3;
    const TOTAL_WEEKS_LOADED = 10;
    const VISIBLE_WEEKS = 2;

    const EVENT_COLOR_MAP = { 'RIG': '#EC4899', 'TEST': '#F97316', 'DE-RIG': '#EC4899', 'ATW': '#64748B', 'BSI': '#3B82F6', 'INACTIVE': '#9CA3AF', 'DEFAULT': '#8B5CF6' };
    let masterTemplateEventMap = {};
    let masterHardwareAssignments = {};
    let masterAvailableHardwareKits = [];

    function getEventDef(engineType, eventName) {
        const map = masterTemplateEventMap[engineType] || masterTemplateEventMap['default'] || {};
        return map[eventName] || map[eventName?.toUpperCase()] || null;
    }
    const TAILWIND_AVATAR_COLORS = [ 'bg-blue-100 text-blue-700', 'bg-pink-100 text-pink-700', 'bg-indigo-100 text-indigo-700', 'bg-green-100 text-green-700', 'bg-purple-100 text-purple-700', 'bg-teal-100 text-teal-700', 'bg-orange-100 text-orange-700', 'bg-yellow-100 text-yellow-700', 'bg-red-100 text-red-700', 'bg-gray-200 text-gray-800', 'bg-cyan-100 text-cyan-700', 'bg-lime-100 text-lime-700' ];
    const QUALI_STATUS_COLORS = {
        'X': '#10B981',
        'TS': '#F59E0B',
        'TT': '#EF4444',
        'TP': '#6B7280',
        'MANUAL_NO_QUALI': '#FFFFFF',
        'DEFAULT': 'rgba(0, 0, 0, 0.2)'
    };

    const openSapUploadModalBtn = document.getElementById('openSapUploadModalBtn');
    const manualSapUploadModal = document.getElementById('manualSapUploadModal');
    const closeManualSapUploadModalBtn = document.getElementById('closeManualSapUploadModal');
    const cancelManualSapUploadBtn = document.getElementById('cancelManualSapUploadBtn');
    const manualSapDropZone = document.getElementById('manual-sap-drop-zone');
    const manualSapFileInput = document.getElementById('manual-sap-file-input');
    const manualSapUploadStatusDiv = document.getElementById('manual-sap-upload-status');

    let allAvailableGroups = []; 

    const tempGroupsBtn = document.getElementById('tempGroupsBtn');
    const tempGroupsModal = document.getElementById('tempGroupsModal');
    const closeTempGroupsModalBtn = document.getElementById('closeTempGroupsModalBtn');
    const tempGroupsList = document.getElementById('tempGroupsList');
    const applyTempGroupsBtn = document.getElementById('applyTempGroupsBtn');

    tempGroupsBtn.addEventListener('click', () => {
        // Liste bauen
        tempGroupsList.innerHTML = '';
        
        // Alle Gruppen aus den geladenen Mitarbeiterdaten extrahieren, falls noch nicht geschehen
        if (allAvailableGroups.length === 0 && allEmployees.length > 0) {
            allAvailableGroups = [...new Set(allEmployees.map(e => e.quali).filter(Boolean))].sort();
        }

        allAvailableGroups.forEach(group => {
            const isChecked = currentEmployeeGroupFilter.includes(group);
            // Wir markieren die "Original"-Gruppen (aus der Config) vielleicht speziell?
            // Für jetzt: Einfach alle Checkboxen.
            
            const div = document.createElement('div');
            div.className = "flex items-center";
            div.innerHTML = `
                <input id="tg-${group}" type="checkbox" value="${group}" ${isChecked ? 'checked' : ''} class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
                <label for="tg-${group}" class="ml-3 text-sm font-medium text-gray-700 cursor-pointer select-none">${group}</label>
            `;
            tempGroupsList.appendChild(div);
        });

        assignmentModalBackdrop.style.display = 'block';
        tempGroupsModal.style.display = 'block';
    });

    function closeTempGroupsModal() {
        assignmentModalBackdrop.style.display = 'none';
        tempGroupsModal.style.display = 'none';
    }
    
    closeTempGroupsModalBtn.addEventListener('click', closeTempGroupsModal);

    applyTempGroupsBtn.addEventListener('click', () => {
        // Neue Auswahl sammeln
        const selected = Array.from(tempGroupsList.querySelectorAll('input:checked')).map(cb => cb.value);
        
        if (selected.length === 0) {
            alert("Bitte mindestens eine Gruppe auswählen.");
            return;
        }

        // Globalen Filter aktualisieren
        currentEmployeeGroupFilter = selected;
        
        // Neu berechnen & rendern
        closeTempGroupsModal();
        recalculateAndRerenderAssignments();
    });
    function openManualSapUploadModal() {
        manualSapUploadStatusDiv.textContent = '';
        manualSapUploadModal.style.display = 'block';
        assignmentModalBackdrop.style.display = 'block';
    }

    function closeManualSapUploadModal() {
        manualSapUploadModal.style.display = 'none';
        assignmentModalBackdrop.style.display = 'none';
        manualSapDropZone.classList.remove('border-blue-500', 'bg-blue-50');
        manualSapFileInput.value = '';
    }

    async function handleManualSapFileUpload(file) {
        if (!file.name.toLowerCase().endsWith('.csv')) {
            manualSapUploadStatusDiv.textContent = 'Fehler: Es werden nur .csv-Dateien akzeptiert.';
            manualSapUploadStatusDiv.className = 'mt-2 text-sm text-center text-red-600';
            return;
        }

        manualSapUploadStatusDiv.textContent = 'SAP-Datei wird hochgeladen und verarbeitet...';
        manualSapUploadStatusDiv.className = 'mt-2 text-sm text-center text-blue-600';
        sapUpdateBtn.disabled = true;
        openSapUploadModalBtn.disabled = true;

        const formData = new FormData();
        formData.append('sap_file', file);

        try {
            const response = await fetch("{{ url_for('api_manual_sap_update') }}", {
                method: 'POST',
                body: formData
            });

            const result = await response.json();

            if (response.ok) {
                manualSapUploadStatusDiv.textContent = result.message;
                manualSapUploadStatusDiv.className = 'mt-2 text-sm text-center text-green-600';
                setTimeout(async () => {
                    closeManualSapUploadModal();
                    await initializeTimeline();
                    sapUpdateBtn.disabled = false;
                    openSapUploadModalBtn.disabled = false;
                    markChanges();
                }, 2000);
            } else {
                throw new Error(result.error || 'Unbekannter Fehler beim manuellen SAP-Upload.');
            }
        } catch (error) {
            manualSapUploadStatusDiv.textContent = `Fehler: ${error.message}`;
            manualSapUploadStatusDiv.className = 'mt-2 text-sm text-center text-red-600';
            sapUpdateBtn.disabled = false;
            openSapUploadModalBtn.disabled = false;
        }
    }

    openSapUploadModalBtn.addEventListener('click', openManualSapUploadModal);
    closeManualSapUploadModalBtn.addEventListener('click', closeManualSapUploadModal);
    cancelManualSapUploadBtn.addEventListener('click', closeManualSapUploadModal);
    manualSapDropZone.addEventListener('click', () => manualSapFileInput.click());
    manualSapFileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { handleManualSapFileUpload(e.target.files[0]); } });
    manualSapDropZone.addEventListener('dragover', (e) => { e.preventDefault(); manualSapDropZone.classList.add('border-blue-500', 'bg-blue-50'); });
    manualSapDropZone.addEventListener('dragleave', (e) => { e.preventDefault(); manualSapDropZone.classList.remove('border-blue-500', 'bg-blue-50'); });
    manualSapDropZone.addEventListener('drop', (e) => { e.preventDefault(); manualSapDropZone.classList.remove('border-blue-500', 'bg-blue-50'); if (e.dataTransfer.files.length > 0) { handleManualSapFileUpload(e.dataTransfer.files[0]); } });

    const createProjectModal = document.getElementById('createProjectModal');
    const createProjectBtn = document.getElementById('createProjectBtn');
    const closeCreateProjectModalBtn = document.getElementById('closeCreateProjectModal');
    const submitNewProjectBtn = document.getElementById('submitNewProjectBtn');
    const createProjectForm = document.getElementById('createProjectForm');

    function openCreateProjectModal() { createProjectForm.reset(); assignmentModalBackdrop.style.display = 'block'; createProjectModal.style.display = 'block'; }
    function closeCreateProjectModal() { assignmentModalBackdrop.style.display = 'none'; createProjectModal.style.display = 'none'; }

    createProjectBtn.addEventListener('click', openCreateProjectModal);
    closeCreateProjectModalBtn.addEventListener('click', closeCreateProjectModal);

    submitNewProjectBtn.addEventListener('click', async () => {
        const projectData = {
            name: document.getElementById('newProjectName').value,
            engine_type: document.getElementById('newEngineType').value,
            project_code: document.getElementById('newProjectCode').value,
            engine_serial: document.getElementById('newEngineSerial').value,
            customer: document.getElementById('newCustomer').value,
            events_start_date: document.getElementById('newEventsStartDate').value
        };
        if (!projectData.name || !projectData.engine_type || !projectData.project_code || !projectData.engine_serial || !projectData.events_start_date) { alert("Bitte füllen Sie alle Pflichtfelder aus."); return; }
        try {
            const response = await fetch(`/api/create_manual_project/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectData) });
            const result = await response.json();
            if (!response.ok) { throw new Error(result.error || 'Unbekannter Fehler beim Erstellen des Projekts.'); }
            alert(result.message);
            closeCreateProjectModal();
            await initializeTimeline();
        } catch (error) { alert(`Fehler: ${error.message}`); }
    });

    function setupTagInput(inputField, getSuggestionsFn, initialTags = [], color = 'slate', onChangeCallback = null) {
        const container = inputField.parentElement;
        let suggestions = [];
    
        const createTag = (value) => {
            const chip = document.createElement('span');
            chip.className = `inline-flex items-center rounded-md bg-${color}-100 px-2 py-1 text-xs font-medium text-${color}-700 ring-1 ring-inset ring-${color}-200 tag-chip`;
            chip.dataset.value = value;
            chip.innerHTML = `${value}<button type="button" class="ml-1.5 text-${color}-500 hover:text-${color}-700"><span class="material-icons-outlined text-sm">close</span></button>`;
            chip.querySelector('button').addEventListener('click', () => {
                chip.remove();
                if (onChangeCallback) onChangeCallback();
            });
            container.insertBefore(chip, inputField);
        };
        
        initialTags.forEach(createTag);
    
        const dropdown = document.createElement('div');
        dropdown.className = 'absolute z-10 mt-1 w-full bg-white shadow-lg rounded-md border border-slate-200 max-h-40 overflow-y-auto hidden';
        container.parentElement.style.position = 'relative'; // Wichtig für die Positionierung des Dropdowns
        container.parentElement.appendChild(dropdown);
    
        const showSuggestions = () => {
            const currentValue = inputField.value.toLowerCase();
            const currentTags = Array.from(container.querySelectorAll('.tag-chip')).map(t => t.dataset.value);
            suggestions = getSuggestionsFn().filter(s => !currentTags.includes(s) && s.toLowerCase().includes(currentValue));
            
            dropdown.innerHTML = '';
            if (suggestions.length > 0) {
                suggestions.forEach(s => {
                    const item = document.createElement('div');
                    item.className = 'px-3 py-2 cursor-pointer hover:bg-slate-100 text-sm';
                    item.textContent = s;
                    item.addEventListener('click', () => {
                        createTag(s);
                        inputField.value = '';
                        dropdown.classList.add('hidden');
                        inputField.focus();
                        if (onChangeCallback) onChangeCallback();
                    });
                    dropdown.appendChild(item);
                });
                dropdown.classList.remove('hidden');
            } else {
                dropdown.classList.add('hidden');
            }
        };
    
        inputField.addEventListener('input', showSuggestions);
        inputField.addEventListener('focus', showSuggestions);
        inputField.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && suggestions.length > 0) {
                e.preventDefault();
                createTag(suggestions[0]);
                inputField.value = '';
                showSuggestions();
                if (onChangeCallback) onChangeCallback();
            }
        });
    
        // Custom Event Listener, um Vorschläge von außen zu aktualisieren
        inputField.addEventListener('update-suggestions', (e) => {
            // Diese Funktion wird vom Engine-Type Input getriggert
            getSuggestionsFn = () => e.detail;
        });
    
        document.addEventListener('click', (e) => {
            if (!container.parentElement.contains(e.target)) {
                dropdown.classList.add('hidden');
            }
        });
    }
    
    function addRuleRow(engineTypes = [], eventTitles = [], requiredSkills = []) {
        const ruleDiv = document.createElement('div');
        ruleDiv.className = 'grid grid-cols-[2fr_2fr_3fr_auto] gap-x-4 items-start p-3 border-b border-slate-100 last:border-b-0 rule-row';
    
        const createTagInputContainer = (label, type) => {
            const wrapper = document.createElement('div');
            wrapper.className = `${type}-container`;
            wrapper.innerHTML = `<label class="block text-xs font-medium text-slate-500 mb-1">${label}</label>`;
            const inputContainer = document.createElement('div');
            inputContainer.className = 'flex flex-wrap items-center gap-2 p-1.5 border border-slate-300 rounded-md bg-white focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500 min-h-[38px]';
            inputContainer.innerHTML = `<input type="text" placeholder="+ Add..." class="flex-grow p-1 border-0 focus:ring-0 text-sm tag-input-field">`;
            wrapper.appendChild(inputContainer);
            return wrapper;
        };
    
        const engineContainer = createTagInputContainer('Engine Type(s)', 'engine-types');
        const eventContainer = createTagInputContainer('Event(s)', 'event-titles');
        const skillsContainer = createTagInputContainer('Benötigte Skills', 'required-skills');
    
        const deleteButton = document.createElement('div');
        deleteButton.className = 'flex justify-end items-start pt-6';
        deleteButton.innerHTML = `<button type="button" class="remove-rule-btn text-red-500 hover:text-red-700 p-2 rounded-full hover:bg-red-50"><span class="material-icons-outlined text-xl">delete_outline</span></button>`;
        deleteButton.querySelector('.remove-rule-btn').addEventListener('click', () => {
            ruleDiv.remove();
            if (document.getElementById('configTableBody').querySelectorAll('.rule-row').length === 0) {
                document.getElementById('configTableBody').innerHTML = '<tr><td colspan="4" class="text-center text-slate-400 py-4">Keine Regeln definiert.</td></tr>';
            }
        });
        
        ruleDiv.appendChild(engineContainer);
        ruleDiv.appendChild(eventContainer);
        ruleDiv.appendChild(skillsContainer);
        ruleDiv.appendChild(deleteButton);
    
        const engineInput = engineContainer.querySelector('input');
        const eventInput = eventContainer.querySelector('input');
        const skillInput = skillsContainer.querySelector('input');
    
        // Engine Types
        setupTagInput(engineInput, () => Object.keys(engineEventMapping).sort(), engineTypes, 'sky', () => {
            const selectedEngines = Array.from(engineContainer.querySelectorAll('.tag-chip')).map(t => t.dataset.value);
            const eventSuggestions = (selectedEngines.length === 0)
                ? [...new Set(Object.values(engineEventMapping).flat())].sort()
                : [...new Set(selectedEngines.flatMap(eng => engineEventMapping[eng] || []))].sort();
            eventInput.dispatchEvent(new CustomEvent('update-suggestions', { detail: eventSuggestions }));
        });
        
        // Events
        setupTagInput(eventInput, () => {
            const selectedEngines = Array.from(engineContainer.querySelectorAll('.tag-chip')).map(t => t.dataset.value);
            if (selectedEngines.length === 0) return [...new Set(Object.values(engineEventMapping).flat())].sort();
            return [...new Set(selectedEngines.flatMap(eng => engineEventMapping[eng] || []))].sort();
        }, eventTitles, 'indigo');
    
        // Benötigte Skills
        setupTagInput(skillInput, () => allAvailableSkills, requiredSkills, 'emerald');
    
        const tableBody = document.getElementById('configTableBody');
        if (!tableBody.querySelector('.rule-row')) tableBody.innerHTML = '';
        tableBody.appendChild(ruleDiv);
    }
    
    function renderSettingsModalContent() {
        configModalBody.innerHTML = `
            <div style="display:none">
                <label class="block text-sm font-medium leading-6 text-gray-900">Mitarbeitergruppen für Zuweisung</label>
                <p class="text-xs text-gray-500 mb-2">Wähle die Gruppen aus, die für die automatische Zuweisung und im Footer berücksichtigt werden.</p>
                <div id="employeeGroupCheckboxes" class="max-h-32 overflow-y-auto space-y-2 rounded-md border border-gray-200 p-3"></div>
            </div>
            <div style="display:none">
                <label class="block text-sm font-medium leading-6 text-gray-900">Testzellen für Zuweisung</label>
                <p class="text-xs text-gray-500 mb-2">Wähle die Testzellen aus, die für "Test"-Events automatisch verplant werden sollen.</p>
                <div id="testCellCheckboxes" class="max-h-32 overflow-y-auto space-y-2 rounded-md border border-gray-200 p-3"></div>
            </div>
            <div class="mt-6 pt-6 border-t border-gray-200">
                <div class="flex justify-between items-center mb-2">
                    <div>
                        <label class="block text-sm font-medium leading-6 text-gray-900">Qualifikationsregeln für Zuweisung</label>
                        <p class="text-xs text-gray-500">Definiere, welcher Skill für welchen Task und Triebwerkstyp benötigt wird.</p>
                    </div>
                    <button id="addRuleBtn" class="text-sm font-medium text-blue-600 hover:text-blue-800">+ Neue Regel hinzufügen</button>
                </div>
                <div id="configTableBody" class="divide-y divide-gray-200 rounded-lg border border-gray-200 bg-white">
                    <!-- Regel-Zeilen werden hier dynamisch eingefügt -->
                </div>
            </div>`;

        // Mitarbeitergruppen und Testzellen befüllen (unverändert)
        const groupCheckboxesContainer = document.getElementById('employeeGroupCheckboxes');
        const allGroups = [...new Set(allEmployees.map(e => e.quali).filter(Boolean))].sort();
        allGroups.forEach(group => { const isChecked = currentEmployeeGroupFilter.includes(group); const checkboxId = `group-checkbox-${group}`; const label = document.createElement('label'); label.className = 'flex items-center space-x-3 p-1 rounded-md hover:bg-gray-50 cursor-pointer'; label.setAttribute('for', checkboxId); label.innerHTML = `<input id="${checkboxId}" name="group_filter" type="checkbox" value="${group}" ${isChecked ? 'checked' : ''} class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"><span class="text-sm text-gray-700">${group}</span>`; groupCheckboxesContainer.appendChild(label); });

        const cellCheckboxesContainer = document.getElementById('testCellCheckboxes');
        allLocations.forEach(loc => { const isChecked = currentTestCellFilter.includes(loc.ID); const checkboxId = `cell-checkbox-${loc.ID}`; const title = `Kompatibel mit: ${loc.TYPES.join(', ') || 'Alle'}`; const label = document.createElement('label'); label.className = `flex items-center space-x-3 p-1 rounded-md hover:bg-gray-50 cursor-pointer`; label.setAttribute('for', checkboxId); label.title = title; label.innerHTML = `<input id="${checkboxId}" name="cell_filter" type="checkbox" value="${loc.ID}" ${isChecked ? 'checked' : ''} class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"><span class="text-sm text-gray-700">${loc.TESTCELL} (${loc.ID})</span>`; cellCheckboxesContainer.appendChild(label); });

        // Regeln rendern
        const tableBody = document.getElementById('configTableBody');
        if (assignmentRules.length > 0) {
            assignmentRules.forEach(rule => {
                addRuleRow(
                    rule.engine_types || [],      // Muss ein Array sein
                    rule.event_titles || [],      // Muss ein Array sein
                    rule.required_skills || []      // Muss ein Array sein
                );
            });
        } else {
            tableBody.innerHTML = '<tr><td colspan="4" class="text-center text-slate-400 py-4">Keine Regeln definiert.</td></tr>';
        }
        
        // Listener für "Neue Regel" Button
        document.getElementById('addRuleBtn').addEventListener('click', () => addRuleRow());
    }

    function createTagHtml(tag, type, index, color) { return `<span class="inline-flex items-center rounded-md bg-${color}-100 px-2 py-1 text-xs font-medium text-${color}-700 ring-1 ring-inset ring-${color}-200">${tag}<button onclick="removeTag(${index}, '${type}', '${tag}')" class="ml-1.5 text-${color}-500 hover:text-${color}-700"><span class="material-icons-outlined text-sm">close</span></button></span>`; }
    function createAddButtonHtml(type, index) { return `<button onclick="addTag(${index}, '${type}')" aria-label="Add tag" class="flex items-center justify-center rounded-full h-6 w-6 bg-slate-200 text-slate-600 hover:bg-slate-300 hover:text-slate-700 transition-colors"><span class="material-icons-outlined text-base">add</span></button>`; }

    function showSelectModal(title, options, callback) {
        const existingModal = document.getElementById('selectTagModal'); if (existingModal) existingModal.remove();
        const modalOverlay = document.createElement('div'); modalOverlay.id = 'selectTagModal'; modalOverlay.className = 'fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4';
        let optionsHtml = options.map(opt => `<option value="${opt}">${opt}</option>`).join('');
        modalOverlay.innerHTML = `<div class="w-full max-w-md rounded-lg bg-white shadow-xl p-6"><h3 class="text-lg font-medium text-gray-900 mb-4">${title}</h3><select id="tagSelect" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">${optionsHtml}</select><div class="mt-6 flex justify-end space-x-3"><button id="cancelSelectBtn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300">Abbrechen</button><button id="confirmSelectBtn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Hinzufügen</button></div></div>`;
        document.body.appendChild(modalOverlay);
        const selectEl = document.getElementById('tagSelect'), confirmBtn = document.getElementById('confirmSelectBtn'), cancelBtn = document.getElementById('cancelSelectBtn');
        const closeModal = () => modalOverlay.remove();
        confirmBtn.onclick = () => { callback(selectEl.value); closeModal(); };
        cancelBtn.onclick = closeModal;
        modalOverlay.onclick = (e) => { if (e.target === modalOverlay) closeModal(); };
    }

    window.addTag = (index, type) => {
        if (type === 'required_skills') {
            const currentSkillsInRule = new Set(assignmentRules[index][type]);
            const availableOptions = ruleOptions.skills.filter(opt => !currentSkillsInRule.has(opt));
            if (availableOptions.length === 0) { alert("Alle verfügbaren Skills wurden dieser Regel bereits hinzugefügt."); return; }
            showSelectModal('Benötigten Skill auswählen', availableOptions, (selectedValue) => { if (selectedValue) { assignmentRules[index][type].push(selectedValue); renderSettingsModalContent(); } });
        } else {
            const options = ruleOptions[type];
            const value = prompt(`Neuen Wert für "${type}" hinzufügen.\nVerfügbare Optionen: ${options.join(', ')}`);
            if (value && value.trim()) { const trimmedValue = value.trim().toUpperCase(); if (!assignmentRules[index][type].includes(trimmedValue)) { assignmentRules[index][type].push(trimmedValue); renderSettingsModalContent(); } else { alert(`Der Wert "${trimmedValue}" ist bereits in dieser Regel vorhanden.`); } }
        }
    };

    window.removeTag = (index, type, tag) => { assignmentRules[index][type] = assignmentRules[index][type].filter(t => t !== tag); renderSettingsModalContent(); };
    window.addRule = () => { assignmentRules.push({ rule_id: '', event_titles: [], engine_types: [], required_skills: [] }); renderSettingsModalContent(); };
    window.removeRule = (index) => { if (confirm('Möchten Sie diese Regel wirklich löschen?')) { assignmentRules.splice(index, 1); renderSettingsModalContent(); } };

    openConfigBtn.addEventListener('click', async () => {
        try {
            // +++ ANFANG DER ÄNDERUNG +++
            // Rufe BEIDE APIs gleichzeitig auf
            const [rulesResponse, optionsResponse] = await Promise.all([
                fetch(`/api/assignment_rules/${timelineId}`),
                fetch(`/api/process_template_options`) // Die neue API
            ]);

            if (!rulesResponse.ok || !optionsResponse.ok) {
                throw new Error('Konfiguration konnte nicht geladen werden.');
            }
            
            const rulesData = await rulesResponse.json();
            engineEventMapping = await optionsResponse.json(); // Speichere die Optionen global
            
            allAvailableSkills = rulesData.options.skills || [];
            assignmentRules = rulesData.rules;
            // +++ ENDE DER ÄNDERUNG +++

            renderSettingsModalContent(); // Diese Funktion passen wir als nächstes an
            configModal.style.display = 'block'; 
            assignmentModalBackdrop.style.display = 'block';
        } catch (error) { 
            console.error("Fehler beim Laden der Einstellungen:", error); 
            alert("Fehler beim Laden der Konfiguration."); 
        }
    });

    function closeConfigModal() { configModal.style.display = 'none'; assignmentModalBackdrop.style.display = 'none'; }
    closeConfigBtn.addEventListener('click', closeConfigModal);

    saveConfigBtn.addEventListener('click', async () => {
        const selectedGroups = Array.from(document.querySelectorAll('#employeeGroupCheckboxes input:checked')).map(cb => cb.value);
        currentEmployeeGroupFilter = selectedGroups;
        const selectedCells = Array.from(document.querySelectorAll('#testCellCheckboxes input:checked')).map(cb => cb.value);
        currentTestCellFilter = selectedCells;
    
        const newRules = [];
        document.querySelectorAll('#configTableBody .rule-row').forEach(row => {
            const engineTypes = Array.from(row.querySelectorAll('.engine-types-container .tag-chip')).map(t => t.dataset.value);
            const eventTitles = Array.from(row.querySelectorAll('.event-titles-container .tag-chip')).map(t => t.dataset.value);
            const requiredSkills = Array.from(row.querySelectorAll('.required-skills-container .tag-chip')).map(t => t.dataset.value);
    
            // Statt Komma-getrenntem Text lesen wir jetzt auch die Skills als Pillen aus
            // Falls das Textfeld noch benutzt wird, kann man es als Fallback nehmen:
            // const skillsFromInput = row.querySelector('input[name="required_skills"]').value.split(',').map(s => s.trim()).filter(Boolean);
            // const allSkills = [...new Set([...requiredSkillsFromPills, ...skillsFromInput])];
    
            if (requiredSkills.length > 0) {
                newRules.push({
                    rule_id: '',
                    event_titles: eventTitles,
                    engine_types: engineTypes,
                    required_skills: requiredSkills
                });
            }
        });
        assignmentRules = newRules;
        
        try {
            const response = await fetch(`/api/assignment_rules/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rules: assignmentRules }) });
            if (!response.ok) throw new Error('Fehler beim Speichern der Regeln.');
            alert("Einstellungen erfolgreich gespeichert!"); closeConfigModal();
            await recalculateAndRerenderAssignments();
        } catch (error) { alert(error.message); }
    });

    function getStartOfWeek(d) { const date = new Date(d); const day = date.getUTCDay() || 7; if (day !== 1) { date.setUTCHours(-24 * (day - 1)); } date.setUTCHours(0, 0, 0, 0); return date; }
    function getProjectPriority(projectId) { const tasks = masterTaskData[projectId] || []; if (tasks.length === 0) return 12; const priorities = []; const today = new Date(); const startOfCurrentWeek = getStartOfWeek(today); const startOfNextWeek = new Date(startOfCurrentWeek.getTime() + 604800000); const startOfLastWeek = new Date(startOfCurrentWeek.getTime() - 604800000); for (const task of tasks) { const taskStartDate = new Date(masterTimelineStartDate.getTime() + task.start * 43200000); if (taskStartDate >= startOfCurrentWeek && taskStartDate < startOfNextWeek) { priorities.push(1); continue; } const startOfNextNextWeek = new Date(startOfNextWeek.getTime() + 604800000); if (taskStartDate >= startOfNextWeek && taskStartDate < startOfNextNextWeek) { priorities.push(2); continue; } if (taskStartDate >= startOfLastWeek && taskStartDate < startOfCurrentWeek) { priorities.push(3); continue; } let isFuture = false; for (let i = 2; i <= 6; i++) { const startFutureWeek = new Date(startOfCurrentWeek.getTime() + i * 604800000); const endFutureWeek = new Date(startFutureWeek.getTime() + 604800000); if (taskStartDate >= startFutureWeek && taskStartDate < endFutureWeek) { priorities.push(4 + i); isFuture = true; break; } } if (isFuture) continue; } return priorities.length > 0 ? Math.min(...priorities) : 12; }
    function handleSortProjects() { if (masterProjectOrder.length === 0) return; masterProjectOrder.sort((a, b) => { const prioA = getProjectPriority(a); const prioB = getProjectPriority(b); return prioA - prioB; }); filterAndRender(); markChanges(); alert('Projekte wurden nach Priorität sortiert. Bitte speichern, um die neue Reihenfolge zu übernehmen.'); }

    function isTestCellBlocked(cellId, isoDate, shift) {
        if (!cellId || !allLocations) return null;
        const cell = allLocations.find(loc => loc.ID === cellId);
        if (!cell || !cell.events) return null;
        
        // Suche nach Blockern (Prio 1) oder Maintenance (Prio 2)
        const activeEvent = cell.events.find(e => {
            if (e.type !== 'Maintenance' && e.type !== 'Blocked') return false;
            const startOk = (e.start_date < isoDate) || (e.start_date === isoDate && e.start_shift <= shift);
            const endOk = (e.end_date > isoDate) || (e.end_date === isoDate && e.end_shift >= shift);
            return startOk && endOk;
        });
        
        return activeEvent; // Gibt das Event-Objekt oder undefined zurück
    }

    function convertApiDataToMasterFormat(apiData) {
        masterTimelineStartDate = new Date(apiData.timeline_config.start_date + 'T00:00:00.000Z');
        masterTotalColumns = apiData.timeline_config.num_days * apiData.timeline_config.columns_per_day;
        masterApiDataCache = apiData.projects;
        allEmployees = apiData.employees;
        allQualifications = apiData.qualifications;
        allLocations = apiData.locations || [];
        manualAssignments.clear();
        prepareEmployeeData();
        masterTaskData = {}; masterProjectDetails = {}; masterProjectOrder = [];
        const serverAssignments = apiData.calculated_data.assignments;
        apiData.projects.forEach(proj => {
            const projectId = `${proj.project_code}_${proj.engine_serial}`;
            masterProjectOrder.push(projectId);
            masterProjectDetails[projectId] = { ...proj };
            masterTaskData[projectId] = [];
            (proj.events || []).forEach(event => {
                if (!event.start_datetime || !event.end_datetime) return;
                const eventStart = new Date(event.start_datetime); const eventEnd = new Date(event.end_datetime);
                const startDiffMs = eventStart.getTime() - masterTimelineStartDate.getTime();
                const startColumn = Math.round(startDiffMs / 43200000);
                const durationMs = eventEnd.getTime() - eventStart.getTime();
                const span = Math.max(1, Math.round(durationMs / 43200000));
                const assigned_employees_per_slot = [], is_manual_per_slot = [], test_cell_id_per_slot = [];
                for (let i = 0; i < span; i++) {
                    const eventSlotKey = `${event.id}_${i}`;
                    const assignmentInfo = serverAssignments[eventSlotKey];
                    if (assignmentInfo) {
                        assigned_employees_per_slot.push(assignmentInfo.employees);
                        is_manual_per_slot.push(assignmentInfo.is_manual);
                        test_cell_id_per_slot.push(assignmentInfo.test_cell_id || null);
                    } else { assigned_employees_per_slot.push([]); is_manual_per_slot.push(false); test_cell_id_per_slot.push(null); }
                }
                masterTaskData[projectId].push({ id: event.id, name: event.title, start: startColumn, span: span, originalEvent: { ...event }, assigned_employees: assigned_employees_per_slot, is_manual: is_manual_per_slot, test_cell_id: test_cell_id_per_slot });
            });
        });
        filteredProjectOrder = [...masterProjectOrder];
    }

    function convertMasterDataToApiFormat() { const projectDetailsMap = new Map(masterApiDataCache.map(p => [`${p.project_code}_${p.engine_serial}`, p])); const orderedProjectsToSave = masterProjectOrder.map(projectId => { const originalProject = projectDetailsMap.get(projectId); if (!originalProject) return null; const updatedProject = JSON.parse(JSON.stringify(originalProject)); const localTasks = masterTaskData[projectId] || []; updatedProject.events = []; localTasks.forEach(localTask => { const startMs = masterTimelineStartDate.getTime() + localTask.start * 43200000; const endMs = startMs + localTask.span * 43200000; const startDt = new Date(startMs); const endDt = new Date(endMs); const apiEvent = { ...localTask.originalEvent, start_datetime: startDt.toISOString(), end_datetime: endDt.toISOString(), start: startDt.toISOString().split('T')[0], start_shift: startDt.getUTCHours() < 12 ? 'AM' : 'PM', end: endDt.toISOString().split('T')[0], end_shift: endDt.getUTCHours() < 12 ? 'AM' : 'PM', }; updatedProject.events.push(apiEvent); }); return updatedProject; }).filter(Boolean); return orderedProjectsToSave; }
    function generateEmployeeColor(initials) { if (employeeColorMap.has(initials)) return employeeColorMap.get(initials); let hash = 0; for (let i = 0; i < initials.length; i++) { hash = initials.charCodeAt(i) + ((hash << 5) - hash); } const colorIndex = Math.abs(hash % TAILWIND_AVATAR_COLORS.length); const colorClass = TAILWIND_AVATAR_COLORS[colorIndex]; employeeColorMap.set(initials, colorClass); return colorClass; }
    function prepareEmployeeData() { employeeQualificationsMap.clear(); allQualifications.forEach(q => { if (q.id) employeeQualificationsMap.set(q.id, q); }); employeeAvailabilityMap.clear(); allEmployees.forEach(emp => { if (!emp.id) return; const empId = emp.id.toLowerCase(); generateEmployeeColor(emp.initials); const availability = {}; (emp.weeks || []).forEach(week => { const shiftFromCsv = (week.Schicht || '').toUpperCase(); let webShift = null; if (shiftFromCsv === 'F') webShift = 'AM'; else if (shiftFromCsv === 'S') webShift = 'PM'; if (!webShift) return; (week.days || []).forEach(day => { if (day.date) { const dateObj = new Date(day.date); if (isNaN(dateObj.getTime())) return; const year = dateObj.getUTCFullYear(); const month = String(dateObj.getUTCMonth() + 1).padStart(2, '0'); const dayOfMonth = String(dateObj.getUTCDate()).padStart(2, '0'); const isoKey = `${year}-${month}-${dayOfMonth}`; const isAvailable = !(day.ereignis || '').trim(); availability[isoKey] = { shift: webShift, available: isAvailable }; } }); }); employeeAvailabilityMap.set(emp.id, availability); }); }
    function findTask(projectId, taskId) { return masterTaskData[projectId]?.find(t => t.id === taskId); }

    function createAvatarsHTML(employeeIds, isManual, project, task, testCellId, slotDate, slotShift) {
        // 1. Mitarbeiter-Avatare generieren (Runde Kreise)
        let employeeAvatarsHtml = '';
        if (employeeIds && employeeIds.length > 0) {
            const avatars = employeeIds.map(empId => {
                const employee = allEmployees.find(e => e.id.toLowerCase() === empId.toLowerCase());
                if (!employee) { 
                    return `<div class="employee-avatar" title="Unbekannt: ${empId}" style="background-color: red;">?</div>`; 
                }

                const initial = employee.initials; 
                let bgColor = QUALI_STATUS_COLORS.DEFAULT; 
                let textColor = 'white'; 
                let borderColor = 'rgba(0,0,0,0.1)';

                const status = masterQualStatuses[task.id]?.[empId.toLowerCase()] || null;

                if (status && QUALI_STATUS_COLORS[status]) { 
                    bgColor = QUALI_STATUS_COLORS[status]; 
                    if (bgColor === '#FFFFFF') { textColor = '#ef4444'; borderColor = '#ef4444'; } 
                } else if (isManual) { 
                    bgColor = QUALI_STATUS_COLORS.MANUAL_NO_QUALI; 
                    textColor = '#ef4444'; 
                    borderColor = '#ef4444'; 
                }

                const manualDotHtml = isManual ? '<div class="manual-assignment-dot"></div>' : '';
                return `<div class="employee-avatar" title="${initial}" style="background-color: ${bgColor}; color: ${textColor}; border-color: ${borderColor};">${initial}${manualDotHtml}</div>`;
            }).join('');

            employeeAvatarsHtml = `<div class="employee-avatars-container">${avatars}</div>`;
        } else {
            // Platzhalter, damit das Layout nicht springt, wenn keine MA da sind (optional)
            employeeAvatarsHtml = `<div class="employee-avatars-container" style="height: 16px;"></div>`;
        }

    // 2. Testzellen-Badge generieren (Rechteckig)
        let testCellHtml = '';
        if (testCellId) {
            const location = allLocations.find(loc => loc.ID === testCellId);
            const cellName = location ? location.TESTCELL : testCellId;

            // HIER DIE ÄNDERUNG: Wir nutzen jetzt strikt die ID für die Anzeige
            const displayName = testCellId; 

            const blockingEvent = isTestCellBlocked(testCellId, slotDate, slotShift);
            const isBlocked = !!blockingEvent;
            const bgColorStr = isBlocked ? '#dc2626' : '#475569';
            
            let titleText = cellName;
            if (isBlocked) {
                // Nutze den Titel aus dem Event ("⛔ Sperre: ...")
                titleText = `${cellName} (KONFLIKT)\n${blockingEvent.title}`;
                if (blockingEvent.description) titleText += `\n${blockingEvent.description}`;
            }

            testCellHtml = `<div class="test-cell-badge" style="background-color: ${bgColorStr};" title="${titleText}">${displayName}</div>`;
        }

        // 3. Hardware-Kit-Badge (status-based colour)
        let hardwareKitHtml = '';
        const hwAssignment = masterHardwareAssignments[task.id];
        if (hwAssignment && hwAssignment.code) {
            const HW_STATUS_COLORS = { 'ready': '#475569', 'partial': '#EF4444', 'missing': '#EF4444' };
            const hwBadgeColor = HW_STATUS_COLORS[hwAssignment.status] || '#0e7490';
            const hwStatusLabel = { 'ready': 'vollständig', 'partial': 'unvollständig', 'missing': 'fehlend' }[hwAssignment.status] || 'unbekannt';
            const manualDot = hwAssignment.is_manual ? '<div class="manual-assignment-dot"></div>' : '';
            hardwareKitHtml = `<div class="test-cell-badge" style="background-color:${hwBadgeColor};font-family:monospace;" title="Hardware-Kit: ${hwAssignment.name} (${hwStatusLabel})">${hwAssignment.code}${manualDot}</div>`;
        }

        return employeeAvatarsHtml + testCellHtml + hardwareKitHtml;
    }

    function renderHeader(offsetWeeks) { const header = document.getElementById('unifiedHeader'); header.innerHTML = ''; if (!masterTimelineStartDate) return; const headerDatesRow = document.createElement('tr'); headerDatesRow.className = 'header-row-dates'; const headerShiftsRow = document.createElement('tr'); headerShiftsRow.className = 'header-row-shifts'; const projectTh = document.createElement('th'); projectTh.className = 'sticky-col header-cell-project'; projectTh.rowSpan = 2; projectTh.innerHTML = 'Projekt'; headerDatesRow.appendChild(projectTh); const days = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']; const numDays = VISIBLE_WEEKS * 7; const displayStartDate = new Date(masterTimelineStartDate.getTime() + offsetWeeks * 604800000); for (let i = 0; i < numDays; i++) { const currentDate = new Date(displayStartDate.getTime() + i * 86400000); const isWeekend = currentDate.getUTCDay() === 0 || currentDate.getUTCDay() === 6; const isHoliday = !isWeekend && freeDaysSet.has(currentDate.toISOString().slice(0,10)); const dateCell = document.createElement('th'); dateCell.colSpan = 2; dateCell.className = "header-cell-date"; if (isWeekend) dateCell.classList.add('weekend-header'); else if (isHoliday) dateCell.classList.add('holiday-header'); dateCell.innerHTML = `<div>${days[currentDate.getUTCDay()]}, ${currentDate.toLocaleDateString('de-DE', {day: '2-digit', month: '2-digit'})}</div>`; headerDatesRow.appendChild(dateCell); ['AM', 'PM'].forEach(shift => { const shiftCell = document.createElement('th'); shiftCell.className = "header-cell-shift"; if (isWeekend) shiftCell.classList.add('weekend-header'); else if (isHoliday) shiftCell.classList.add('holiday-header'); shiftCell.innerHTML = shift; headerShiftsRow.appendChild(shiftCell); }); } header.appendChild(headerDatesRow); header.appendChild(headerShiftsRow); }

    function renderBody(offsetWeeks) {
        const body = document.getElementById('unifiedBody'); body.innerHTML = '';
        const visibleStartCol = offsetWeeks * 14;
        const visibleEndCol = visibleStartCol + (VISIBLE_WEEKS * 14);
        filteredProjectOrder.forEach(projectId => {
            const row = document.createElement('tr'); row.dataset.projectRowId = projectId; const proj = masterProjectDetails[projectId]; if (proj && !proj.active) row.classList.add('opacity-60');
            const projectNameCell = document.createElement('td'); projectNameCell.className = 'sticky-col project-name-cell'; projectNameCell.draggable = true; projectNameCell.dataset.dragType = 'project-row'; projectNameCell.dataset.projectId = projectId;
            const activeClass = proj.active ? '' : 'opacity-60'; const activeBtnClass = proj.active ? 'bg-green-500 hover:bg-green-600' : 'bg-red-500 hover:bg-red-600'; const activeBtnText = proj.active ? 'Aktiv' : 'Inaktiv';
            const commentCount = proj.comment_count || 0; const countBadgeHtml = commentCount > 0 ? `<span class="comment-count-badge">${commentCount}</span>` : '';

// --- Buttons definieren ---
            const addBlockBtnHtml = `<button class="add-new-block-btn" data-project-id="${projectId}" title="Neuen Block hinzufügen"><span class="material-icons-outlined">add_circle_outline</span></button>`;
            const commentBtnHtml = `<button class="open-comments-btn" data-project-id="${projectId}" data-project-name="${proj.name}" title="Kommentare"><span class="material-icons-outlined">chat_bubble_outline</span>${countBadgeHtml}</button>`;

            const isManualProject = (!proj.BE || proj.BE.trim() === '');
            const manualProjectIcon = isManualProject ? '<span class="material-icons-outlined text-base text-green-600 ml-2" title="Manuell erstelltes Projekt">add_circle</span>' : '';

            // Aktiv/Inaktiv Button bekommt jetzt volle Breite im Flex-Container unten
            const toggleBtnHtml = `<button class="toggle-active-btn ${activeBtnClass} w-full mt-1" data-project-id="${projectId}" title="${proj.active ? 'Projekt deaktivieren' : 'Projekt aktivieren'}">${activeBtnText}</button>`;

            let extraButtonsHtml = '';
            if (!proj.active) { 
                extraButtonsHtml += `<button class="reset-project-btn bg-gray-400 hover:bg-gray-500 ml-1" data-project-id="${projectId}" title="Events zurücksetzen"><span class="material-icons-outlined" style="font-size: 14px; vertical-align: middle;">restart_alt</span></button>`; 
                if (isManualProject) { 
                    extraButtonsHtml += `<button class="delete-project-btn bg-red-600 hover:bg-red-700 text-white ml-1" data-project-id="${projectId}" title="Projekt löschen"><span class="material-icons-outlined" style="font-size: 14px; vertical-align: middle;">delete_forever</span></button>`; 
                } 
            }

            // --- NEUES LAYOUT FÜR DIE PROJEKT-ZELLE ---
            // Wir nutzen ein Grid oder Flex-Column für die rechte Seite, um zu stapeln
            projectNameCell.innerHTML = `
            <div class="flex justify-between items-center h-full ${activeClass} w-full pr-2" draggable="false">
                <div class="flex items-center flex-grow min-w-0 mr-2">
                    <div class="truncate">
                        <div class="font-medium text-slate-900 truncate" title="${proj.name}">${proj.name}</div>
                        <div class="text-xs text-slate-500 truncate" title="${proj.engine_type} / ${proj.project_code} / ${proj.engine_serial} - ${proj.customer}">${proj.customer} / ${proj.engine_serial} </div>
                    </div>
                    ${manualProjectIcon}
                </div>
                <div class="flex flex-col items-end flex-shrink-0">
                    <div class="flex items-center mb-1">
                        ${addBlockBtnHtml}
                        ${commentBtnHtml}
                        ${extraButtonsHtml}
                    </div>
                    ${toggleBtnHtml}
                </div>
            </div>`;

            row.appendChild(projectNameCell);
            const cells = Array.from({ length: VISIBLE_WEEKS * 14 }, (_, i) => { const cell = document.createElement('td'); cell.dataset.columnIndex = visibleStartCol + i; const cellDate = new Date(masterTimelineStartDate.getTime() + (visibleStartCol + i) * 43200000); if (cellDate.getUTCDay() === 0 || cellDate.getUTCDay() === 6) { cell.classList.add('weekend-bg'); } else if (freeDaysSet.has(cellDate.toISOString().slice(0,10))) { cell.classList.add('holiday-bg'); } return cell; });
            const projectTasks = masterTaskData[projectId] || []; projectTasks.sort((a, b) => a.start - b.start);
            projectTasks.forEach((task, taskIndex) => {
                const isFirstSegment = taskIndex === 0 || projectTasks[taskIndex - 1].name !== task.name; const isLastSegment = taskIndex === projectTasks.length - 1 || projectTasks[taskIndex + 1].name !== task.name;
                for (let i = 0; i < task.span; i++) {
                    const globalColIndex = task.start + i;
                    if (globalColIndex >= visibleStartCol && globalColIndex < visibleEndCol) {
                        const slotDateTime = new Date(masterTimelineStartDate.getTime() + globalColIndex * 43200000);
                        const slotDateIso = slotDateTime.toISOString().split('T')[0];
                        const slotShift = slotDateTime.getUTCHours() < 12 ? 'AM' : 'PM';
                        const localColIndex = globalColIndex - visibleStartCol; const cell = cells[localColIndex]; cell.innerHTML = '';
                        let roundingClass = ''; if (isFirstSegment && i === 0) roundingClass += ' rounded-l-md'; if (isLastSegment && i === task.span - 1) roundingClass += ' rounded-r-md';

                        // --- ANFANG DER KORREKTUR ---
                        const employeesForSegment = (task.assigned_employees && task.assigned_employees[i]) ? task.assigned_employees[i] : [];
                        const isManualForSegment = (task.is_manual && task.is_manual[i]) ? task.is_manual[i] : false;
                        // NEU: Testzelle für diesen spezifischen Slot auslesen
                        const testCellForSegment = (task.test_cell_id && task.test_cell_id[i]) ? task.test_cell_id[i] : null;

                        // NEU: Parameter für Testzelle an createAvatarsHTML übergeben
                        const avatarsHTML = createAvatarsHTML(employeesForSegment, isManualForSegment, proj, task, testCellForSegment, slotDateIso, slotShift);
                        // --- ENDE DER KORREKTUR ---

                        const taskBar = document.createElement('div'); taskBar.className = 'task-bar'; taskBar.draggable = proj.active; taskBar.dataset.taskId = task.id; taskBar.dataset.projectId = projectId;
                        const inactiveClass = proj.active ? '' : 'inactive'; const eventTitleUpper = task.name.toUpperCase(); const eventDef = getEventDef(proj.engine_type, task.name); const colorHex = proj.active ? (eventDef ? eventDef.color : (EVENT_COLOR_MAP[eventTitleUpper] || EVENT_COLOR_MAP['DEFAULT'])) : EVENT_COLOR_MAP['INACTIVE'];
                        taskBar.innerHTML = `<div class="task-segment-container ${inactiveClass} ${roundingClass}" style="background-color: ${colorHex};"><div class="task-segment-content"><span class="task-name-display">${task.name}</span>${avatarsHTML}</div></div>${i === task.span - 1 ? `<div class="resize-handle resize-handle-right ${inactiveClass}"></div>` : ''}`;
                        cell.appendChild(taskBar);
                    }
                }
            });
            for (let i = 0; i < projectTasks.length - 1; i++) { const currentTask = projectTasks[i]; const nextTask = projectTasks[i + 1]; const endOfCurrent = currentTask.start + currentTask.span; const startOfNext = nextTask.start; for (let j = endOfCurrent; j < startOfNext; j++) { if (j >= visibleStartCol && j < visibleEndCol) { const localColIndex = j - visibleStartCol; const cell = cells[localColIndex]; if (cell && !cell.hasChildNodes()) { cell.innerHTML = `<div class="connector-line-container"><div class="connector-line"></div></div>`; } } } }
            const milestoneLabels = { 'BE': 'B3', 'TS': 'TS', 'TE': 'TE', 'SA': 'SA' };
            Object.keys(milestoneLabels).forEach(key => { try { const dateStr = proj[key]; if (!dateStr || typeof dateStr !== 'string' || dateStr.trim() === '') return; const parts = dateStr.trim().split(/\s+/).filter(Boolean); if (parts.length === 0) return; const dateParts = parts[0].split('.'); if (dateParts.length < 3) return; const day = parseInt(dateParts[0], 10), month = parseInt(dateParts[1], 10) - 1, year = parseInt(dateParts[2], 10); if (isNaN(day) || isNaN(month) || isNaN(year)) return; let hour = 0; if (parts.length > 1 && parts[1]) { const timeParts = parts[1].split(':'); if (timeParts.length > 1) { const parsedHour = parseInt(timeParts[0], 10); if (!isNaN(parsedHour)) hour = parsedHour; } } const milestoneDate = new Date(Date.UTC(year, month, day, hour)); if (isNaN(milestoneDate.getTime())) return; const diffMs = milestoneDate.getTime() - masterTimelineStartDate.getTime(); const globalColFloat = diffMs / 43200000; if (globalColFloat >= visibleStartCol && globalColFloat < visibleEndCol) { const localColFloat = globalColFloat - visibleStartCol; const targetCellIndex = Math.floor(localColFloat); const targetCell = cells[targetCellIndex]; const fractionInCell = localColFloat - targetCellIndex; const leftPercentage = fractionInCell * 100; if (targetCell) { const marker = document.createElement('div'); marker.className = 'milestone-marker'; marker.title = `${milestoneLabels[key]}: ${dateStr}`; marker.style.left = `${leftPercentage}%`; marker.innerHTML = `<span>${milestoneLabels[key]}</span>`; targetCell.appendChild(marker); } } } catch (error) { console.error(`Fehler bei Meilenstein '${key}' für Projekt '${proj.name}':`, error); } });
            cells.forEach(cell => row.appendChild(cell)); body.appendChild(row);
        });
    }

    async function clearOrphanedManualAssignments() { const orphanedSlots = new Map(); Object.entries(masterFooterData).forEach(([colIndex, slotData]) => { if (!slotData.manual || slotData.manual.length === 0) return; const colDate = new Date(masterTimelineStartDate.getTime() + parseInt(colIndex) * 43200000); const dateStr = colDate.toISOString().split('T')[0]; const shift = colDate.getUTCHours() < 12 ? 'AM' : 'PM'; slotData.manual.forEach(initials => { orphanedSlots.set(`${initials}|${dateStr}|${shift}`, true); }); }); if (orphanedSlots.size === 0) return; const entriesToDelete = []; manualAssignments.forEach(ma => { const emp = allEmployees.find(e => e.id.toLowerCase() === ma.employee_id.toLowerCase()); if (!emp) return; if (orphanedSlots.has(`${emp.initials}|${ma.date_str}|${ma.shift}`)) entriesToDelete.push(ma); }); if (entriesToDelete.length === 0) return; if (!confirm(`${entriesToDelete.length} verwaiste manuelle Zuweisung(en) zurücksetzen?`)) return; try { const response = await fetch(`/api/clear_orphaned_manual_assignments/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entries_to_delete: entriesToDelete }) }); const result = await response.json(); if (response.ok) { await recalculateAndRerenderAssignments(); } else { alert('Fehler: ' + (result.error || 'Zurücksetzen fehlgeschlagen.')); } } catch (error) { console.error("Fehler beim Zurücksetzen:", error); alert("Netzwerkfehler."); } }
    function renderFooter(offsetWeeks, availabilityData) { const footer = document.getElementById('unifiedFooter'); footer.innerHTML = ''; const footerRow = document.createElement('tr'); const availableLabelCell = document.createElement('td'); availableLabelCell.className = 'sticky-col project-name-cell font-semibold'; const hasOrphanedManual = Object.values(availabilityData).some(s => s.manual && s.manual.length > 0); availableLabelCell.innerHTML = `Verfügbar${hasOrphanedManual ? '<button class="clear-orphaned-btn ml-2 text-red-400 hover:text-red-600 transition-colors" title="Verwaiste manuelle Zuweisungen zurücksetzen" style="vertical-align:middle"><span class="material-icons-outlined" style="font-size:15px;line-height:1">playlist_remove</span></button>' : ''}`; if (hasOrphanedManual) { availableLabelCell.querySelector('.clear-orphaned-btn').addEventListener('click', clearOrphanedManualAssignments); } footerRow.appendChild(availableLabelCell); const visibleStartCol = offsetWeeks * 14; const MAX_AVATARS_TO_SHOW = 6; for (let i = 0; i < VISIBLE_WEEKS * 14; i++) { const globalColIndex = visibleStartCol + i; const slotData = availabilityData[globalColIndex] || { available: [], manual: [] }; const cell = document.createElement('td'); cell.dataset.columnIndex = globalColIndex; const cellDate = new Date(masterTimelineStartDate.getTime() + globalColIndex * 43200000); if (cellDate.getUTCDay() === 0 || cellDate.getUTCDay() === 6) { cell.classList.add('weekend-bg'); } else if (freeDaysSet.has(cellDate.toISOString().slice(0,10))) { cell.classList.add('holiday-bg'); } const allPeopleInCell = [...slotData.available, ...slotData.manual]; const peopleToShow = allPeopleInCell.slice(0, MAX_AVATARS_TO_SHOW); const remainingCount = allPeopleInCell.length - peopleToShow.length; const avatarsHTML = peopleToShow.map(initials => { const colorClass = employeeColorMap.get(initials) || 'bg-gray-200 text-gray-800'; const isManual = slotData.manual.includes(initials); const manualDot = isManual ? '<div class="manual-assignment-dot"></div>' : ''; return `<div class="available-employee-avatar ${colorClass}" title="${initials}">${initials}${manualDot}</div>`; }).join(''); let remainingCountHTML = remainingCount > 0 ? `<div class="remaining-count" title="${remainingCount} weitere verfügbar">+${remainingCount}</div>` : ''; cell.innerHTML = `<div><div class="avatars-in-cell-container">${avatarsHTML}</div>${remainingCountHTML}</div>`; if (slotData.manual.length > 0) { cell.classList.add('footer-cell-clickable'); } footerRow.appendChild(cell); } footer.appendChild(footerRow); }
    function filterAndRender() { const searchTerm = searchInput.value.toLowerCase(); if (!searchTerm) { filteredProjectOrder = [...masterProjectOrder]; } else { filteredProjectOrder = masterProjectOrder.filter(projectId => { const proj = masterProjectDetails[projectId]; return proj.name.toLowerCase().includes(searchTerm) || proj.project_code.toLowerCase().includes(searchTerm) || proj.engine_serial.toLowerCase().includes(searchTerm); }); } renderUnifiedTable(); }
    function renderUnifiedTable() { if (!masterApiDataCache) return; renderHeader(currentWeekOffset); renderBody(currentWeekOffset); renderFooter(currentWeekOffset, masterFooterData); updateNavButtons(); updateScrollShadow(); highlightCurrentTimeslotAndScroll(); }

    // ### ANFANG DER ÄNDERUNG ###
    function handleListClick(e) {
        const toggleBtn = e.target.closest('.toggle-active-btn');
        const resetBtn = e.target.closest('.reset-project-btn');
        const commentBtn = e.target.closest('.open-comments-btn');
        const deleteBtn = e.target.closest('.delete-project-btn');
        const addBtn = e.target.closest('.add-new-block-btn'); // Neuer Button

        if (toggleBtn) handleToggleActive(toggleBtn);
        else if (resetBtn) handleResetProject(resetBtn);
        else if (deleteBtn) handleDeleteProject(deleteBtn);
        else if (addBtn) handleAddNewBlock(addBtn); // Neue Handler-Funktion aufrufen
        else if (commentBtn) {
            const projectId = commentBtn.dataset.projectId;
            const projectName = commentBtn.dataset.projectName;
            openCommentsModal(projectId, projectName);
        }
    }


    async function handleDeleteProject(button) { const projectId = button.dataset.projectId; const proj = masterProjectDetails[projectId]; if (!proj) return; if (confirm(`Möchten Sie das Projekt "${proj.name}" wirklich löschen?`)) { delete masterProjectDetails[projectId]; delete masterTaskData[projectId]; masterProjectOrder = masterProjectOrder.filter(id => id !== projectId); masterApiDataCache = masterApiDataCache.filter(p => `${p.project_code}_${p.engine_serial}` !== projectId); markChanges(); const saved = await saveChanges(true); if (saved) { alert("Projekt wurde gelöscht."); initializeTimeline(); } else { alert("Projekt konnte nicht gelöscht werden."); } } }
    function handleToggleActive(button) { const projectId = button.dataset.projectId; if (masterProjectDetails[projectId]) { masterProjectDetails[projectId].active = !masterProjectDetails[projectId].active; const apiProject = masterApiDataCache.find(p => `${p.project_code}_${p.engine_serial}` === projectId); if (apiProject) { apiProject.active = masterProjectDetails[projectId].active; } markChanges(); recalculateAndRerenderAssignments(); } }
    async function handleResetProject(button) { // <-- Funktion wird async
        const projectId = button.dataset.projectId;
        const proj = masterProjectDetails[projectId];
    
        if (!proj || proj.active || !confirm(`Events für "${proj.name}" zurücksetzen?`)) {
            return;
        }
    
        const tsDateStr = proj.TS;
        if (!tsDateStr) {
            alert("Kein 'TS' Datum für dieses Projekt gefunden. Zurücksetzen nicht möglich.");
            return;
        }
        
        // --- ANFANG DER NEUEN LOGIK ---
        
        // TS-Datum parsen, um es an die API zu senden
        const parts = tsDateStr.trim().split(/\s+/).filter(Boolean);
        const dateParts = parts[0].split('.');
        if (dateParts.length < 3) {
            alert("Ungültiges TS-Datum.");
            return;
        }
        const day = parseInt(dateParts[0], 10), month = parseInt(dateParts[1], 10) - 1, year = parseInt(dateParts[2], 10);
        let hour = 8; // Standard 8 Uhr
        if (parts.length > 1 && parts[1]) {
            const timeParts = parts[1].split(':');
            if (timeParts.length > 1) hour = parseInt(timeParts[0], 10);
        }
        // WICHTIG: Sende das Datum als UTC ISO String, damit die Zeitzone im Backend stimmt
        const tsDateUTC = new Date(Date.UTC(year, month, day, hour));
        if (isNaN(tsDateUTC.getTime())) {
            alert("TS-Datum konnte nicht verarbeitet werden.");
            return;
        }
    
        try {
            const encodedDate = encodeURIComponent(tsDateUTC.toISOString());
            const response = await fetch(`/api/generate_events_from_template/${encodeURIComponent(proj.engine_type)}/${encodedDate}`);
            if (!response.ok) {
                const error = await response.json();
                throw new Error(error.error || 'Serverfehler beim Generieren der Events.');
            }
            const eventTemplates = await response.json();
    
            // Die neuen Events in das richtige Format für die Timeline umwandeln
            const newEvents = [];
            eventTemplates.forEach(eventTpl => {
                const startDt = new Date(`${eventTpl.start}T${eventTpl.start_shift === 'AM' ? '00' : '12'}:00:00.000Z`);
                const endDt = new Date(`${eventTpl.end}T${eventTpl.end_shift === 'AM' ? '12' : '24'}:00:00.000Z`);
    
                const startDiffMs = startDt.getTime() - masterTimelineStartDate.getTime();
                const startColumn = Math.round(startDiffMs / 43200000);
                const span = Math.round((endDt - startDt) / 43200000);
                
                const newId = `${projectId}_${eventTpl.title}_${startDt.toISOString()}`;
                
                const assigned_employees_per_slot = Array.from({ length: span }, () => []);
                const is_manual_per_slot = Array.from({ length: span }, () => false);
                const test_cell_id_per_slot = Array.from({ length: span }, () => null);
    
                newEvents.push({
                    id: newId,
                    name: eventTpl.title,
                    start: startColumn,
                    span: span,
                    assigned_employees: assigned_employees_per_slot,
                    is_manual: is_manual_per_slot,
                    test_cell_id: test_cell_id_per_slot,
                    originalEvent: {
                        id: newId,
                        title: eventTpl.title,
                        start: eventTpl.start,
                        start_shift: eventTpl.start_shift,
                        end: eventTpl.end,
                        end_shift: eventTpl.end_shift,
                        start_datetime: startDt.toISOString(),
                        end_datetime: endDt.toISOString(),
                        color: eventTpl.color
                    }
                });
            });
            
            masterTaskData[projectId] = newEvents;
            markChanges();
            recalculateAndRerenderAssignments();
            alert("Events wurden basierend auf dem Template zurückgesetzt. Bitte speichern.");
    
        } catch (error) {
            alert("Fehler beim Zurücksetzen der Events: " + error.message);
        }
        // --- ENDE DER NEUEN LOGIK ---
    }    
    function handleDrop(e) {
        e.preventDefault();
        e.stopPropagation();

        // Visuelle Highlights entfernen
        document.querySelectorAll('.drop-target-highlight, .row-drop-target-highlight').forEach(el => { 
            el.classList.remove('drop-target-highlight', 'row-drop-target-highlight'); 
        });

        const targetCell = e.target.closest('td');
        if (!targetCell || !activeDragData) return;

        const targetRow = targetCell.closest('tr');
        if (!targetRow) return;
        const targetProjectId = targetRow.dataset.projectRowId;

        // --- Logik für Projekt-Sortierung ---
        if (activeDragData.type === 'project-row') {
             if (!targetProjectId) return;
             const sourceProjectId = activeDragData.projectId; 
             if (targetProjectId !== sourceProjectId) {
                 const sourceIndex = masterProjectOrder.indexOf(sourceProjectId); 
                 const targetIndex = masterProjectOrder.indexOf(targetProjectId);
                 if (sourceIndex === -1 || targetIndex === -1) return;

                 const [movedProject] = masterProjectOrder.splice(sourceIndex, 1);
                 const newTargetIndex = masterProjectOrder.indexOf(targetProjectId);

                 if (sourceIndex < targetIndex) { 
                     masterProjectOrder.splice(newTargetIndex + 1, 0, movedProject); 
                 } else { 
                     masterProjectOrder.splice(newTargetIndex, 0, movedProject); 
                 } 
                 markChanges(); 
                 filterAndRender();
             }
             return;
        }

        // --- Logik für Task-Verschiebung ---
        if (activeDragData.type === 'task') {
            const targetTimelineCell = e.target.closest('td:not(.sticky-col)');
            if (!targetTimelineCell) return;

            const dropColIndex = parseInt(targetTimelineCell.dataset.columnIndex, 10);
            const delta = dropColIndex - activeDragData.leaderOriginalStart;

            if (delta === 0) return; 

            let canMoveAll = true;

            for (const item of activeDragData.group) {
                const currentTask = item.task;
                const newStart = currentTask.start + delta;
                const newEnd = newStart + currentTask.span;

                if (newStart < 0 || newEnd > masterTotalColumns) {
                    canMoveAll = false; 
                    break;
                }

                const projectTasks = masterTaskData[item.projectId];
                for (const t of projectTasks) {
                    if (selectedTaskIds.has(t.id)) continue;
                    if (newStart < (t.start + t.span) && newEnd > t.start) {
                        canMoveAll = false; 
                        break;
                    }
                }
                if (!canMoveAll) break;
            }

            if (canMoveAll) {
                activeDragData.group.forEach(item => {
                    item.task.start += delta;
                });
                markChanges();
                recalculateAndRerenderAssignments();

                // ### FIX: HIERFÜGEN WIR DAS HINZU ###
                clearSelection(); 
                // #####################################
            } else {
                console.log("Verschieben blockiert: Kollision oder außerhalb des Bereichs");
            }
        }
    }
    async function recalculateAndRerenderAssignments() { const currentProjectsState = convertMasterDataToApiFormat(); try { const payload = { projects: currentProjectsState, assignment_group: currentEmployeeGroupFilter, selected_test_cells: currentTestCellFilter }; const response = await fetch(`/api/v2/recalculate_assignments/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error("Neuberechnung fehlgeschlagen."); } const responseData = await response.json(); if (responseData.template_event_map) { masterTemplateEventMap = responseData.template_event_map; } if (responseData.hardware_assignments) { masterHardwareAssignments = responseData.hardware_assignments; } if (responseData.available_hardware_kits) { masterAvailableHardwareKits = responseData.available_hardware_kits; } const newAssignments = responseData.calculated_data.assignments; for (const projectId in masterTaskData) { masterTaskData[projectId].forEach(task => { for (let i = 0; i < task.span; i++) { const eventSlotKey = `${task.id}_${i}`; const assignmentInfo = newAssignments[eventSlotKey]; if (assignmentInfo) { task.assigned_employees[i] = assignmentInfo.employees; task.is_manual[i] = assignmentInfo.is_manual; if (task.test_cell_id) { task.test_cell_id[i] = assignmentInfo.test_cell_id || null; } } else { task.assigned_employees[i] = []; task.is_manual[i] = false; if (task.test_cell_id) { task.test_cell_id[i] = null; } } } }); } masterBlockedData = responseData.calculated_data.blocked_employees || {}; masterFooterData = responseData.calculated_data.footer_availability; manualAssignments.clear(); if (responseData.manual_assignments) { responseData.manual_assignments.forEach(ma => { const key = `${ma.project_code}_${ma.engine_serial}_${ma.event_title}_${ma.date_str}_${ma.shift}_${ma.employee_id.toLowerCase()}`; manualAssignments.set(key, ma); }); } renderBody(currentWeekOffset); renderFooter(currentWeekOffset, masterFooterData); highlightCurrentTimeslotAndScroll(); updateScrollShadow(); } catch (error) { console.error("Fehler bei Echtzeit-Aktualisierung:", error); alert("Fehler bei der Mitarbeiterzuweisung."); } }
    function handleDragOver(e) {
        e.preventDefault();
        if (!activeDragData) return;

        // Alte Highlights entfernen
        document.querySelectorAll('.drop-target-highlight, .row-drop-target-highlight').forEach(el => { 
            el.classList.remove('drop-target-highlight', 'row-drop-target-highlight'); 
        });

        // --- Vorschau für Projekt-Sortierung ---
        if (activeDragData.type === 'project-row') {
             const targetRow = e.target.closest('tr');
             if (targetRow) {
                 document.body.classList.add('is-row-dragging');
                 e.dataTransfer.dropEffect = 'move';
                 targetRow.classList.add('row-drop-target-highlight');
             }
             return;
        }

        // --- Vorschau für Task-Verschiebung ---
        if (activeDragData.type === 'task') {
            document.body.classList.remove('is-row-dragging');

            const targetTimelineCell = e.target.closest('td:not(.sticky-col)');
            if (!targetTimelineCell) { 
                e.dataTransfer.dropEffect = 'none'; 
                return; 
            }

            // Delta berechnen (wie weit hat sich die Maus vom Startpunkt des Leaders bewegt)
            const hoverColIndex = parseInt(targetTimelineCell.dataset.columnIndex, 10);
            const delta = hoverColIndex - activeDragData.leaderOriginalStart;

            // Grobe Validierung für die Vorschau: Fliegt irgendwas aus dem Bild?
            // (Kollisionsprüfung ist hier meist zu teuer für "jedes Pixel Mausbewegung", 
            // daher prüfen wir nur die Grenzen)
            let canDrop = true;
             for (const item of activeDragData.group) {
                const newStart = item.task.start + delta;
                const newEnd = newStart + item.task.span;
                if (newStart < 0 || newEnd > masterTotalColumns) { 
                    canDrop = false; 
                    break; 
                }
            }

            if (canDrop) {
                e.dataTransfer.dropEffect = 'move';

                // Berechne den sichtbaren Bereich, um Performance zu sparen
                // (Wir markieren nur Zellen, die man auch sieht)
                const visibleStartCol = currentWeekOffset * 14;
                const visibleEndCol = visibleStartCol + (VISIBLE_WEEKS * 14);

                // Iteriere über ALLE bewegten Tasks und markiere ihre Zielposition
                activeDragData.group.forEach(item => {
                     const newStart = item.task.start + delta;

                     // Finde die HTML-Zeile (TR) für das Projekt dieses Tasks
                     const row = document.querySelector(`tr[data-project-row-id="${item.projectId}"]`);

                     if (row) {
                         // Markiere die Zellen für die Länge (Span) des Tasks
                         for(let i=0; i < item.task.span; i++) {
                             const colIdx = newStart + i;
                             // Nur markieren, wenn im sichtbaren Bereich
                             if (colIdx >= visibleStartCol && colIdx < visibleEndCol) {
                                 const cell = row.querySelector(`td[data-column-index="${colIdx}"]`);
                                 if(cell) cell.classList.add('drop-target-highlight');
                             }
                         }
                     }
                });
            } else {
                e.dataTransfer.dropEffect = 'none';
            }
        }
    }
    function handleDragEnd(e) { document.body.classList.remove('is-dragging', 'is-row-dragging'); document.querySelectorAll('.is-dragging-segment').forEach(el => el.classList.remove('is-dragging-segment')); activeDragData = null; isDraggingFlag = false; }
    function handleDragStart(e) {
        if (isResizing) { 
            e.preventDefault(); 
            return; 
        }

        const dragTarget = e.target;
        const taskBar = dragTarget.closest('.task-bar');

        // Fall 1: Ein Task wird gezogen
        if (taskBar) {
            const projectId = taskBar.dataset.projectId;
            // Prüfen ob Projekt aktiv ist
            if (!masterProjectDetails[projectId] || !masterProjectDetails[projectId].active) { 
                e.preventDefault(); 
                return; 
            }

            e.stopPropagation();
            const taskId = taskBar.dataset.taskId;

            // Wenn der gezogene Task NICHT Teil der aktuellen Auswahl ist,
            // löschen wir die alte Auswahl und wählen nur diesen neuen Task aus.
            // Das ist Standard-Verhalten in Betriebssystemen.
            if (!selectedTaskIds.has(taskId)) {
                clearSelection();
                toggleTaskSelection(taskId, true);
            }

            const leaderTask = findTask(projectId, taskId);

            // Wir erstellen eine Liste aller Tasks, die bewegt werden sollen.
            // Dazu iterieren wir über alle IDs in selectedTaskIds und suchen die Objekte.
            const movingTasks = [];

            // Performance-Hinweis: Wir gehen durch alle Projekte, um die Tasks zur ID zu finden.
            // Da die ID eindeutig ist, ist das sicher.
            selectedTaskIds.forEach(selId => {
                for (const pId in masterTaskData) {
                    const foundTask = masterTaskData[pId].find(t => t.id === selId);
                    if (foundTask) {
                        movingTasks.push({
                            task: foundTask,
                            projectId: pId,
                            // WICHTIG: Wir speichern den Abstand (Offset) relativ zum gezogenen Element ("Leader")
                            offsetCols: foundTask.start - leaderTask.start 
                        });
                        break; // Task gefunden, weiter zum nächsten
                    }
                }
            });

            // Das Drag-Daten-Objekt
            activeDragData = { 
                type: 'task', 
                leaderTaskId: taskId,        // Die ID des Elements unter der Maus
                leaderProjectId: projectId,  // Das Projekt des Elements unter der Maus
                leaderOriginalStart: leaderTask.start, // Startposition vor dem Drag
                leaderSpan: leaderTask.span,
                group: movingTasks           // Die gesamte Gruppe inkl. Leader
            };

            e.dataTransfer.setData('text/plain', JSON.stringify({ taskId, projectId }));
            e.dataTransfer.effectAllowed = 'move';

            // Visuelles Feedback: Wir setzen die Klasse 'is-dragging-segment' auf alle
            // bewegten Elemente, damit sie halbtransparent werden.
            setTimeout(() => {
                selectedTaskIds.forEach(id => {
                    document.querySelectorAll(`[data-task-id='${id}']`).forEach(el => el.classList.add('is-dragging-segment'));
                });
                document.body.classList.add('is-dragging');
            }, 0);

            return;
        }

        // Fall 2: Eine ganze Projekt-Zeile wird sortiert (Sortier-Handle links)
        const projectCell = dragTarget.closest('.project-name-cell[data-drag-type="project-row"]'); 
        if (projectCell) {
            e.stopPropagation();
            const projectId = projectCell.dataset.projectId;
            activeDragData = { type: 'project-row', projectId };
            e.dataTransfer.setData('text/plain', JSON.stringify({ projectId }));
            e.dataTransfer.effectAllowed = 'move';
            setTimeout(() => {
                document.body.classList.add('is-dragging');
            }, 0);
        }
    }
    function handleConnectorClick(e) { const connectorContainer = e.target.closest('.connector-line-container'); if (!connectorContainer) return; e.preventDefault(); e.stopPropagation(); const cell = connectorContainer.closest('td'); const row = cell.closest('tr'); const projectId = row.dataset.projectRowId; const clickedColumnIndex = parseInt(cell.dataset.columnIndex, 10); if (!projectId || isNaN(clickedColumnIndex) || !masterProjectDetails[projectId].active) return; const taskName = prompt("Name für neuen Block (z.B. TEST):"); if (!taskName || taskName.trim() === '') return; const projectTasks = [...masterTaskData[projectId]].sort((a, b) => a.start - b.start); const taskBefore = projectTasks.filter(t => (t.start + t.span) <= clickedColumnIndex).pop(); const taskAfter = projectTasks.find(t => t.start > clickedColumnIndex); if (!taskBefore || !taskAfter) { return; } const newStartColumn = taskBefore.start + taskBefore.span; const gapSize = taskAfter.start - newStartColumn; if (gapSize <= 0) { alert("Kein Platz zum Einfügen."); return; } const startDt = new Date(masterTimelineStartDate.getTime() + newStartColumn * 43200000); const endDt = new Date(startDt.getTime() + 43200000); const newId = `${projectId}_task_${Date.now()}`; const newTask = { id: newId, name: taskName.trim(), start: newStartColumn, span: 1, assigned_employees: [[]], is_manual: [false], test_cell_id: [null], originalEvent: { id: newId, title: taskName.trim(), start: startDt.toISOString().split('T')[0], start_shift: startDt.getUTCHours() < 12 ? 'AM' : 'PM', end: endDt.toISOString().split('T')[0], end_shift: endDt.getUTCHours() < 12 ? 'AM' : 'PM', start_datetime: startDt.toISOString(), end_datetime: endDt.toISOString(), color: EVENT_COLOR_MAP[taskName.trim().toUpperCase()] || EVENT_COLOR_MAP['DEFAULT'] } }; masterTaskData[projectId].push(newTask); markChanges(); recalculateAndRerenderAssignments(); }    

    function handleAddNewBlock(button) {
        const projectId = button.dataset.projectId;
        if (!masterProjectDetails[projectId] || !masterProjectDetails[projectId].active) {
            alert("Blöcke können nur zu aktiven Projekten hinzugefügt werden.");
            return;
        }

        const taskName = prompt("Name für neuen Block (z.B. TEST):");
        if (!taskName || taskName.trim() === '') return;

        const now = new Date();
        const nowUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getMinutes()));
        const diffMs = nowUTC.getTime() - masterTimelineStartDate.getTime();

        let newStartColumn = 0; // Fallback, falls aktueller Tag vor Timeline-Start liegt
        if (diffMs >= 0) {
            newStartColumn = Math.floor(diffMs / 43200000); // 43200000ms = 12 Stunden
        }

        const projectTasks = masterTaskData[projectId] || [];

        // NEU: Prüfe auf Kollisionen und finde den nächsten freien Platz ab dem heutigen Tag
        while (projectTasks.some(task => newStartColumn >= task.start && newStartColumn < task.start + task.span)) {
            newStartColumn++; // Gehe zur nächsten Spalte, bis eine freie gefunden wird
        }

        if (newStartColumn >= masterTotalColumns) {
            alert("Kein Platz mehr in der Timeline ab dem heutigen Datum für einen neuen Block.");
            return;
        }

        const startDt = new Date(masterTimelineStartDate.getTime() + newStartColumn * 43200000);
        const endDt = new Date(startDt.getTime() + 43200000); // Standarddauer: 1 Slot (12h)
        const newId = `${projectId}_task_${Date.now()}`;

        const newTask = {
            id: newId,
            name: taskName.trim(),
            start: newStartColumn,
            span: 1,
            assigned_employees: [[]],
            is_manual: [false],
            test_cell_id: [null],
            originalEvent: {
                id: newId,
                title: taskName.trim(),
                start: startDt.toISOString().split('T')[0],
                start_shift: startDt.getUTCHours() < 12 ? 'AM' : 'PM',
                end: endDt.toISOString().split('T')[0],
                end_shift: endDt.getUTCHours() < 12 ? 'AM' : 'PM',
                start_datetime: startDt.toISOString(),
                end_datetime: endDt.toISOString(),
                color: EVENT_COLOR_MAP[taskName.trim().toUpperCase()] || EVENT_COLOR_MAP['DEFAULT']
            }
        };

        masterTaskData[projectId].push(newTask);
        markChanges();
        recalculateAndRerenderAssignments();
    }

function handleMouseDown(e) {
        // 1. Resize hat Vorrang (Größe ändern)
        const handle = e.target.closest('.resize-handle');
        if (handle) {
            const projectId = handle.closest('.task-bar').dataset.projectId;
            if (!masterProjectDetails[projectId] || !masterProjectDetails[projectId].active) return;
            e.preventDefault();
            e.stopPropagation();
            isResizing = true;
            const taskBar = handle.closest('.task-bar');
            const taskId = taskBar.dataset.taskId;
            const task = findTask(projectId, taskId);
            resizingTask = { taskId, projectId, handle, startX: e.clientX, startSpan: task.span, lastDeltaCols: 0 };
            document.body.classList.add('is-resizing');
            document.addEventListener('mousemove', handleMouseMove);
            document.addEventListener('mouseup', handleMouseUp);
            return;
        }

        // 2. Task Click / Drag Vorbereitung
        if (e.target.closest('.task-bar')) {
            return; 
        }

        // ### FIX: Keine Auswahlbox starten, wenn man in die linke Projektspalte klickt ###
        // Die linke Spalte hat die Klasse 'sticky-col'. Wenn der Klick dort passiert, brechen wir ab.
        if (e.target.closest('.sticky-col')) {
            return;
        }
        // ################################################################################

        // 3. Start Selection Box (Nur wenn auf leere Zelle im Zeitbereich geklickt wird)
        if (e.target.closest('#unifiedTableContainer')) {
            isSelecting = true;
            selectionStart = { x: e.clientX, y: e.clientY };

            // Wenn keine Modifier-Taste (Strg/Shift) gedrückt, Auswahl zurücksetzen
            if (!e.ctrlKey && !e.shiftKey) {
                clearSelection();
            }

            selectionMarquee.style.left = e.clientX + 'px';
            selectionMarquee.style.top = e.clientY + 'px';
            selectionMarquee.style.width = '0px';
            selectionMarquee.style.height = '0px';
            selectionMarquee.style.display = 'block';

            document.body.style.userSelect = 'none'; // Textauswahl verhindern
            document.addEventListener('mousemove', handleSelectionMove);
            document.addEventListener('mouseup', handleSelectionUp);
        }
    }
    // Hilfsfunktion: Auswahl leeren
    function clearSelection() {
        selectedTaskIds.clear();
        document.querySelectorAll('.task-bar.selected').forEach(el => el.classList.remove('selected'));
    }

    // Hilfsfunktion: Task zur Auswahl hinzufügen
    function toggleTaskSelection(taskId, forceSelect = null) {
        const els = document.querySelectorAll(`.task-bar[data-task-id="${taskId}"]`);
        if (forceSelect === true || (forceSelect === null && !selectedTaskIds.has(taskId))) {
            selectedTaskIds.add(taskId);
            els.forEach(el => el.classList.add('selected'));
        } else {
            selectedTaskIds.delete(taskId);
            els.forEach(el => el.classList.remove('selected'));
        }
    }

    function handleSelectionMove(e) {
        if (!isSelecting) return;

        const currentX = e.clientX;
        const currentY = e.clientY;

        const width = Math.abs(currentX - selectionStart.x);
        const height = Math.abs(currentY - selectionStart.y);
        const left = Math.min(currentX, selectionStart.x);
        const top = Math.min(currentY, selectionStart.y);

        selectionMarquee.style.width = width + 'px';
        selectionMarquee.style.height = height + 'px';
        selectionMarquee.style.left = left + 'px';
        selectionMarquee.style.top = top + 'px';

        // Kollisionsprüfung live während des Ziehens
        const marqueeRect = selectionMarquee.getBoundingClientRect();

        // Wir iterieren über alle sichtbaren Task-Bars
        document.querySelectorAll('.task-bar').forEach(taskBar => {
            const taskRect = taskBar.getBoundingClientRect();
            const taskId = taskBar.dataset.taskId;

            // Einfache AABB Kollisionsprüfung (Axis-Aligned Bounding Box)
            const intersects = !(
                marqueeRect.right < taskRect.left || 
                marqueeRect.left > taskRect.right || 
                marqueeRect.bottom < taskRect.top || 
                marqueeRect.top > taskRect.bottom
            );

            if (intersects) {
                 // Wenn berührt, temporär markieren (wir nutzen forceSelect=true)
                 if (!selectedTaskIds.has(taskId)) {
                     toggleTaskSelection(taskId, true);
                 }
            }
        });
    }

    function handleSelectionUp(e) {
        isSelecting = false;
        selectionMarquee.style.display = 'none';
        document.body.style.userSelect = '';
        document.removeEventListener('mousemove', handleSelectionMove);
        document.removeEventListener('mouseup', handleSelectionUp);
    }
    function handleMouseMove(e) { if (!isResizing || !resizingTask) return; const task = findTask(resizingTask.projectId, resizingTask.taskId); if (!task) return; const cellWidth = 45; const deltaX = e.clientX - resizingTask.startX; const currentDeltaCols = Math.round(deltaX / cellWidth); if (currentDeltaCols !== resizingTask.lastDeltaCols) { if (resizingTask.handle.classList.contains('resize-handle-right')) { let newSpan = resizingTask.startSpan + currentDeltaCols; if (newSpan < 1) newSpan = 1; const endPos = task.start + newSpan; if (endPos > masterTotalColumns) newSpan = masterTotalColumns - task.start; const nextTask = masterTaskData[resizingTask.projectId].filter(t => t.id !== task.id && t.start > task.start).sort((a,b) => a.start - b.start)[0]; if (nextTask && endPos > nextTask.start) newSpan = nextTask.start - task.start; task.span = newSpan; markChanges(); recalculateAndRerenderAssignments(); resizingTask.lastDeltaCols = currentDeltaCols; } } }
    function handleMouseUp() { if (isResizing) { isResizing = false; resizingTask = null; document.body.classList.remove('is-resizing'); document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); } }
    function markChanges() { saveBtn.dataset.hasChanges = "true"; saveBtn.classList.replace('bg-blue-600', 'bg-orange-500'); saveBtn.classList.replace('hover:bg-blue-700', 'hover:bg-orange-600'); }
    
    let masterBlockedData = {}; 
    async function initializeTimeline() {
        document.getElementById('unifiedBody').innerHTML = '<tr><td class="p-4 text-center" colspan="29">Lade Daten...</td></tr>';
        try {

            const [response, fdResp] = await Promise.all([
                fetch(`/api/v2/timeline_data/${timelineId}`),
                fetch('/api/free-days')
            ]);
            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
            const apiData = await response.json();
            if (fdResp.ok) { try { freeDaysSet = new Set(await fdResp.json()); } catch(e){} }

            masterFooterData = apiData.calculated_data.footer_availability;
            masterQualStatuses = apiData.qualification_statuses_for_tasks || {};
            masterBlockedData = apiData.calculated_data.blocked_employees || {};
            if (apiData.template_event_map) { masterTemplateEventMap = apiData.template_event_map; }
            if (apiData.hardware_assignments) { masterHardwareAssignments = apiData.hardware_assignments; }
            if (apiData.available_hardware_kits) { masterAvailableHardwareKits = apiData.available_hardware_kits; }

            manualAssignments.clear();
            if (apiData.manual_assignments) {
                apiData.manual_assignments.forEach(ma => {
                    const key = `${ma.project_code}_${ma.engine_serial}_${ma.event_title}_${ma.date_str}_${ma.shift}_${ma.employee_id.toLowerCase()}`;
                    manualAssignments.set(key, ma);
                });
            }
            allEmployees = apiData.employees;
            // NEU: Gruppen extrahieren
            allAvailableGroups = [...new Set(allEmployees.map(e => e.quali).filter(Boolean))].sort();
            convertApiDataToMasterFormat(apiData);

            if (allLocations.length > 0) {
                // Das Backend prüft pro Slot, ob eine Zelle blockiert ist.
                // Daher ist es sicher und korrekt, hier initial alle Zellen zu übergeben.
                currentTestCellFilter = allLocations.map(loc => loc.ID);
            }


            filterAndRender();
            if (apiData.timeline_config && apiData.timeline_config.assignment_groups) {
                 currentEmployeeGroupFilter = apiData.timeline_config.assignment_groups;
            } else {
                 // Fallback, falls die API noch nicht angepasst ist
                 currentEmployeeGroupFilter = ['FF'];
                 console.warn("Keine 'assignment_groups' in der API-Antwort gefunden. Fallback auf ['FF'].");
            }
        } catch (error) { 
            console.error("Fehler beim Initialisieren der Timeline:", error); 
            document.getElementById('unifiedBody').innerHTML = '<tr><td class="p-4 text-red-500 text-center" colspan="29">Fehler beim Laden.</td></tr>';
        }
    }

    async function saveChanges(forceSave = false) { if (!forceSave && saveBtn.dataset.hasChanges !== "true") { return true; } const payload = { projects: convertMasterDataToApiFormat() }; try { const response = await fetch(`/api/save_editable_timeline_data/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const result = await response.json(); if (response.ok) { if (!forceSave) alert(result.message || "Gespeichert!"); saveBtn.dataset.hasChanges = "false"; saveBtn.classList.replace('bg-orange-500', 'bg-blue-600'); saveBtn.classList.replace('hover:bg-orange-600', 'hover:bg-blue-700'); return true; } else { alert('Fehler: ' + (result.error || 'Unbekannt.')); return false; } } catch (error) { console.error("Netzwerkfehler beim Speichern:", error); alert("Netzwerkfehler."); return false; } }
    async function handleSapUpdate() { if (saveBtn.dataset.hasChanges === "true") { if (confirm("Ungespeicherte Änderungen. Vor dem SAP-Update speichern?")) { await saveChanges(); if (saveBtn.dataset.hasChanges === "true") { alert("Speichern fehlgeschlagen. Update abgebrochen."); return; } } else { alert("Update abgebrochen."); return; } } sapUpdateBtn.disabled = true; sapUpdateBtn.innerHTML = `<span class="material-icons-outlined text-base -ml-0.5 animate-spin">sync</span>Aktualisiere...`; try { const response = await fetch(`/api/update_from_sap/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (response.ok) { alert(result.message || "SAP-Update abgeschlossen!"); initializeTimeline(); } else { alert('Fehler: ' + (result.error || 'Serverfehler.')); } } catch (error) { console.error("Netzwerkfehler:", error); alert("Netzwerkfehler."); } finally { sapUpdateBtn.disabled = false; sapUpdateBtn.innerHTML = `<span class="material-icons-outlined text-base -ml-0.5">sync</span>SAP-Update`; } }
    function updateNavButtons() { prevWeekBtn.disabled = currentWeekOffset <= 0; nextWeekBtn.disabled = currentWeekOffset >= (TOTAL_WEEKS_LOADED - VISIBLE_WEEKS); }
    function updateScrollShadow() { const container = document.getElementById('unifiedTableContainer'); if (!container) return; const { scrollTop, scrollHeight, clientHeight } = container; const canScrollDown = scrollHeight > clientHeight && scrollTop + clientHeight < scrollHeight - 1; if (canScrollDown) container.classList.add('is-scrollable-down'); else container.classList.remove('is-scrollable-down'); }
    function highlightCurrentTimeslotAndScroll() { document.querySelectorAll('.current-timeslot-highlight').forEach(el => el.classList.remove('current-timeslot-highlight')); if (!masterTimelineStartDate) return; const now = new Date(); const nowUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getMinutes())); const diffMs = nowUTC.getTime() - masterTimelineStartDate.getTime(); if (diffMs < 0) return; const currentGlobalCol = Math.floor(diffMs / 43200000); const visibleStartCol = currentWeekOffset * 14; const visibleEndCol = visibleStartCol + (VISIBLE_WEEKS * 14); if (currentGlobalCol < visibleStartCol || currentGlobalCol >= visibleEndCol) return; document.querySelectorAll(`#unifiedBody tr, #unifiedFooter tr`).forEach(row => { const cellToHighlight = row.querySelector(`td[data-column-index='${currentGlobalCol}']`); if (cellToHighlight) { cellToHighlight.classList.add('current-timeslot-highlight'); } }); const cellWidth = 45; const container = document.getElementById('unifiedTableContainer'); const scrollTarget = currentGlobalCol * cellWidth - (container.offsetWidth / 3); container.scrollLeft = Math.max(0, scrollTarget); }
    function openAssignmentModal(data) { 
        currentModalData = data; 
        modalTitle.textContent = `Mitarbeiter für "${data.event.name}" zuweisen`; 
        modalSubtitle.textContent = `Projekt: ${data.project.name} | Slot: ${data.slotDate.toLocaleDateString('de-DE')} ${data.slotShift}`; 
        deleteAssignmentsBtn.style.display = data.isManual ? 'inline-flex' : 'none'; 
        availableEmployeesSelect.innerHTML = '<option value="">-- Mitarbeiter auswählen --</option>'; 
        
        const currentAssignedIds = new Set(data.assignedEmployeeIds.map(id => id.toLowerCase())); 
        
        // Slot Key bauen
        const slotKey = `${data.slotDate.toISOString().split('T')[0]}_${data.slotShift}`;
        
        // Globale Blockaden holen
        const globalBlockedIds = new Set(masterBlockedData[slotKey] || []);

        const manuallyBlockedElsewhereIds = new Set(); 
        manualAssignments.forEach(ma => { 
            const isSameSlot = ma.date_str === data.slotDate.toISOString().split('T')[0] && ma.shift === data.slotShift; 
            const isThisTask = ma.project_code === data.project.project_code && ma.engine_serial === data.project.engine_serial && ma.event_title === data.event.name; 
            if (isSameSlot && !isThisTask) { 
                const blockedEmp = allEmployees.find(e => e.id.toLowerCase() === ma.employee_id.toLowerCase()); 
                if(blockedEmp) manuallyBlockedElsewhereIds.add(blockedEmp.id.toLowerCase()); 
            } 
        }); 
        
        const statusMap = { 'X': { icon: '🟢', title: 'Experte' }, 'TS': { icon: '🟡', title: 'Training gestartet' }, 'TT': { icon: '🔴', title: 'Training überfällig' }, 'TP': { icon: '⚪️', title: 'Training geplant' }, 'DEFAULT': { icon: '➖', title: 'Nicht qualifiziert' } }; 
        const groupsToFilter = Array.isArray(currentEmployeeGroupFilter) ? currentEmployeeGroupFilter : [currentEmployeeGroupFilter]; 
        
        allEmployees.forEach(emp => { 
            const empId = emp.id.toLowerCase(); 
            
            // CHECK: Ist er global blockiert?
            const isGlobalBlocked = globalBlockedIds.has(empId);

            if (!currentAssignedIds.has(empId) && !manuallyBlockedElsewhereIds.has(empId) && !isGlobalBlocked && groupsToFilter.includes(emp.quali)) { 
                const empAvailability = employeeAvailabilityMap.get(empId) || {}; 
                const dayInfo = empAvailability[data.slotDate.toISOString().split('T')[0]]; 
                if (dayInfo && dayInfo.available && dayInfo.shift === data.slotShift) { 
                    const option = document.createElement('option'); 
                    option.value = empId; 
                    const status = masterQualStatuses[data.event.id]?.[empId] || null; 
                    const indicator = statusMap[status] || statusMap['DEFAULT']; 
                    option.textContent = `${indicator.icon} ${emp.name} (${emp.initials})`; 
                    option.title = indicator.title; 
                    availableEmployeesSelect.appendChild(option); 
                } 
            } 
        }); 
        
        renderAssignedEmployeesInModal(data.assignedEmployeeIds, data.isManual);

        // Hardware-Kit section
        const eventDef = getEventDef(data.project.engine_type, data.event.name);
        const hwSection = document.getElementById('hardwareKitSection');
        const hwAddSection = document.getElementById('hardwareKitAddSection');
        const hwSelect = document.getElementById('hardwareKitSelect');
        if (eventDef && eventDef.hardware) {
            hwSection.classList.remove('hidden');
            hwAddSection.classList.remove('hidden');
            hwSelect.innerHTML = '<option value="">-- Auto --</option>';
            (masterAvailableHardwareKits || []).forEach(kit => {
                const opt = document.createElement('option');
                opt.value = kit.id;
                opt.textContent = kit.code ? `[${kit.code}] ${kit.name}` : kit.name;
                hwSelect.appendChild(opt);
            });
            renderAssignedKitInModal(masterHardwareAssignments[data.event.id]);
        } else {
            hwSection.classList.add('hidden');
            hwAddSection.classList.add('hidden');
        }

        assignmentModal.style.display = 'block';
        assignmentModalBackdrop.style.display = 'block';
    }
    function renderAssignedEmployeesInModal(assignedIds, isManualAssignment) { assignedEmployeesContainer.innerHTML = ''; if (assignedIds.length === 0) { assignedEmployeesContainer.innerHTML = '<p class="text-xs text-gray-400 italic">Keine Mitarbeiter zugewiesen.</p>'; return; } assignedIds.forEach(empId => { const employee = allEmployees.find(e => e.id.toLowerCase() === empId.toLowerCase()); if (employee) { const tag = document.createElement('span'); tag.className = 'assigned-employee-tag'; const manualIcon = isManualAssignment ? `<span class="material-icons-outlined text-sm text-blue-500" title="Manuell">push_pin</span>` : `<span class="material-icons-outlined text-sm text-gray-400" title="Automatisch">smart_toy</span>`; tag.innerHTML = `${manualIcon} <span class="mx-1">${employee.initials}</span> <button data-id="${empId.toLowerCase()}" class="remove-employee-btn"><span class="material-icons-outlined text-sm">close</span></button>`; assignedEmployeesContainer.appendChild(tag); } }); }
    function renderAssignedKitInModal(hwEntry) {
        const display = document.getElementById('assignedKitDisplay');
        const hwSelect = document.getElementById('hardwareKitSelect');
        display.innerHTML = '';
        if (hwEntry && hwEntry.kit_id) {
            hwSelect.value = hwEntry.kit_id;
            const tag = document.createElement('span');
            tag.className = 'assigned-employee-tag';
            const icon = hwEntry.is_manual
                ? `<span class="material-icons-outlined text-sm text-blue-500" title="Manuell">push_pin</span>`
                : `<span class="material-icons-outlined text-sm text-gray-400" title="Automatisch">smart_toy</span>`;
            const codeBadge = hwEntry.code ? `<span style="font-size:0.65rem;background:#cffafe;color:#0e7490;border-radius:3px;padding:0 3px;font-family:monospace;font-weight:700;">${hwEntry.code}</span> ` : '';
            const resetBtn = hwEntry.is_manual
                ? `<button class="remove-employee-btn reset-kit-btn" title="Zurück auf Auto"><span class="material-icons-outlined text-sm">close</span></button>`
                : '';
            tag.innerHTML = `${icon} <span class="mx-1">${codeBadge}${hwEntry.name}</span>${resetBtn}`;
            if (hwEntry.is_manual) {
                tag.querySelector('.reset-kit-btn').addEventListener('click', () => saveKitAssignment(null));
            }
            display.appendChild(tag);
        } else {
            display.innerHTML = '<p class="text-xs text-gray-400 italic">Kein Kit zugewiesen.</p>';
            hwSelect.value = '';
        }
    }
    async function saveKitAssignment(kitId) {
        if (!currentModalData) return;
        try {
            const resp = await fetch(`/api/hardware_assignment/${timelineId}`, {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ event_id: currentModalData.event.id, kit_id: kitId || null })
            });
            if (!resp.ok) throw new Error('Fehler beim Speichern');
            closeAssignmentModal();
            await recalculateAndRerenderAssignments();
        } catch (err) { alert('Hardware-Kit konnte nicht gespeichert werden: ' + err.message); }
    }
    addEmployeeBtn.addEventListener('click', () => { const selectedId = availableEmployeesSelect.value; if (selectedId && currentModalData && !currentModalData.assignedEmployeeIds.includes(selectedId)) { currentModalData.assignedEmployeeIds.push(selectedId); currentModalData.isManual = true; renderAssignedEmployeesInModal(currentModalData.assignedEmployeeIds, currentModalData.isManual); Array.from(availableEmployeesSelect.options).forEach(opt => { if (opt.value === selectedId) opt.remove(); }); } });
    assignedEmployeesContainer.addEventListener('click', e => { const removeBtn = e.target.closest('.remove-employee-btn'); if (removeBtn && currentModalData) { const idToRemove = removeBtn.dataset.id; currentModalData.assignedEmployeeIds = currentModalData.assignedEmployeeIds.filter(id => id !== idToRemove); currentModalData.isManual = true; renderAssignedEmployeesInModal(currentModalData.assignedEmployeeIds, currentModalData.isManual); const emp = allEmployees.find(e => e.id.toLowerCase() === idToRemove); if (emp) { if (!availableEmployeesSelect.querySelector(`option[value="${emp.id.toLowerCase()}"]`)) { const option = document.createElement('option'); option.value = emp.id.toLowerCase(); option.textContent = `${emp.name} (${emp.initials})`; availableEmployeesSelect.appendChild(option); } } } });
    function closeAssignmentModal() { assignmentModal.style.display = 'none'; assignmentModalBackdrop.style.display = 'none'; currentModalData = null; }
    function handleDeleteEvent() { if (!currentModalData || !currentModalData.project || !currentModalData.event) return; const { project, event } = currentModalData; if (confirm(`Event "${event.name}" löschen?`)) { const projectId = `${project.project_code}_${project.engine_serial}`; const tasks = masterTaskData[projectId]; if (tasks) { const taskIndex = tasks.findIndex(t => t.id === event.id); if (taskIndex > -1) { tasks.splice(taskIndex, 1); markChanges(); closeAssignmentModal(); recalculateAndRerenderAssignments(); } } } }
    async function saveManualAssignments(assignmentsToSaveIds, slotInfoOverride = null) { const timelineSaved = await saveChanges(true); if (!timelineSaved) { alert("Timeline konnte nicht gespeichert werden. Zuweisung abgebrochen."); return; } const slotInfo = slotInfoOverride || (currentModalData ? { project_code: currentModalData.project.project_code, engine_serial: currentModalData.project.engine_serial, event_title: currentModalData.event.name, date_str: currentModalData.slotDate.toISOString().split('T')[0], shift: currentModalData.slotShift, engine_type: currentModalData.project.engine_type } : null); if (!slotInfo) return; const payload = { slot_info: slotInfo, assignments: assignmentsToSaveIds.map(id => ({ ...slotInfo, employee_id: id })), test_cell_id: assignmentsToSaveIds.length > 0 ? (currentModalData ? currentModalData.testCellId : null) : null }; try { const response = await fetch(`/api/manual_assignment/${timelineId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const result = await response.json(); if (response.ok) { closeAssignmentModal(); closeBlockerModal(); await recalculateAndRerenderAssignments(); } else { alert('Fehler: ' + (result.error || 'Zuweisung konnte nicht gespeichert werden.')); } } catch (error) { console.error("Fehler bei manueller Zuweisung:", error); alert("Netzwerkfehler."); } }
    function updateCommentCountBadge(projectId, newCount) { const commentBtn = document.querySelector(`.open-comments-btn[data-project-id="${projectId}"]`); if (!commentBtn) return; let badge = commentBtn.querySelector('.comment-count-badge'); if (newCount > 0) { if (!badge) { badge = document.createElement('span'); badge.className = 'comment-count-badge'; commentBtn.appendChild(badge); } badge.textContent = newCount; } else { if (badge) { badge.remove(); } } }
    async function openCommentsModal(projectId, projectName) { activeProjectForComments = projectId; commentsModalTitle.textContent = `Kommentare für: ${projectName}`; commentsListContainer.innerHTML = '<p>Lade Kommentare...</p>'; commentsOverlay.classList.remove('hidden'); try { const response = await fetch(`/api/comments/${projectId}`); if (!response.ok) throw new Error('Kommentare konnten nicht geladen werden.'); const comments = await response.json(); renderComments(comments); } catch (error) { commentsListContainer.innerHTML = `<p class="text-red-500">${error.message}</p>`; } }
    function closeCommentsModal() { commentsOverlay.classList.add('hidden'); activeProjectForComments = null; newCommentTextarea.value = ''; }
    function renderComments(comments) { commentsListContainer.innerHTML = ''; if (comments.length === 0) { commentsListContainer.innerHTML = '<p>Keine Kommentare.</p>'; return; } comments.forEach(comment => { const commentDiv = document.createElement('div'); commentDiv.className = 'comment-container relative flex items-start space-x-3'; commentDiv.dataset.commentId = comment.comment_id; const timestamp = new Date(comment.timestamp).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short'}); let actionsHtml = ''; if (comment.author === currentUsername) { actionsHtml = `<div class="comment-actions"><button class="text-gray-400 hover:text-gray-600 p-1" onclick="toggleCommentMenu(event, '${comment.comment_id}')"><span class="material-icons-outlined text-base">more_vert</span></button><div id="menu-${comment.comment_id}" class="comment-actions-menu hidden"><button onclick="editComment('${comment.comment_id}')">Bearbeiten</button><button onclick="deleteComment('${comment.comment_id}')" class="text-red-600">Löschen</button></div></div>`; } commentDiv.innerHTML = `<div class="flex-shrink-0 h-10 w-10 rounded-full bg-slate-200 flex items-center justify-center font-bold text-slate-600 text-sm">${comment.author.substring(0,2).toUpperCase()}</div><div class="flex-grow"><div class="flex items-center space-x-2"><span class="font-semibold text-gray-700">${comment.author}</span><span class="text-xs text-gray-500">${timestamp}</span></div><div class="comment-text-container mt-1"><p class="text-gray-600 bg-gray-50 p-3 rounded-md border border-gray-200 whitespace-pre-wrap">${comment.comment}</p></div></div>${actionsHtml}`; commentsListContainer.appendChild(commentDiv); }); }
    async function postNewComment() { const commentText = newCommentTextarea.value.trim(); if (!commentText || !activeProjectForComments) return; postCommentBtn.disabled = true; try { const response = await fetch(`/api/comments/${activeProjectForComments}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ comment: commentText }) }); if (!response.ok) throw new Error('Kommentar konnte nicht gesendet werden.'); newCommentTextarea.value = ''; const comments = await (await fetch(`/api/comments/${activeProjectForComments}`)).json(); renderComments(comments); updateCommentCountBadge(activeProjectForComments, comments.length); } catch (error) { alert(error.message); } finally { postCommentBtn.disabled = false; } }
    window.toggleCommentMenu = (event, commentId) => { event.stopPropagation(); document.querySelectorAll('.comment-actions-menu').forEach(menu => { if (menu.id !== `menu-${commentId}`) { menu.classList.add('hidden'); } }); const menu = document.getElementById(`menu-${commentId}`); if (menu) menu.classList.toggle('hidden'); }
    window.editComment = (commentId) => { const commentDiv = document.querySelector(`[data-comment-id="${commentId}"]`); const textContainer = commentDiv.querySelector('.comment-text-container'); const currentText = textContainer.querySelector('p').textContent; textContainer.innerHTML = `<textarea class="w-full p-2 border border-blue-400 rounded-md resize-none text-gray-600" rows="3">${currentText}</textarea><div class="mt-2 flex justify-end space-x-2"><button onclick="cancelEdit('${commentId}', \`${currentText}\`)" class="bg-gray-200 hover:bg-gray-300 text-sm font-medium px-3 py-1 rounded-md">Abbrechen</button><button onclick="saveEdit('${commentId}')" class="bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium px-3 py-1 rounded-md">Speichern</button></div>`; }
    window.cancelEdit = (commentId, originalText) => { const commentDiv = document.querySelector(`[data-comment-id="${commentId}"]`); const textContainer = commentDiv.querySelector('.comment-text-container'); textContainer.innerHTML = `<p class="text-gray-600 bg-gray-50 p-3 rounded-md border border-gray-200 whitespace-pre-wrap">${originalText}</p>`; }
    window.saveEdit = async (commentId) => { const commentDiv = document.querySelector(`[data-comment-id="${commentId}"]`); const textarea = commentDiv.querySelector('textarea'); const newText = textarea.value.trim(); if (!newText) { alert("Kommentar darf nicht leer sein."); return; } try { const response = await fetch(`/api/comments/entry/${commentId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ comment: newText }) }); const result = await response.json(); if (!response.ok) throw new Error(result.error || 'Fehler beim Speichern.'); const textContainer = commentDiv.querySelector('.comment-text-container'); textContainer.innerHTML = `<p class="text-gray-600 bg-gray-50 p-3 rounded-md border border-gray-200 whitespace-pre-wrap">${result.comment}</p>`; const timestampEl = commentDiv.querySelector('.text-xs.text-gray-500'); if (timestampEl) { timestampEl.textContent = new Date(result.timestamp).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short'}) + ' (bearbeitet)'; } } catch (error) { alert(`Fehler: ${error.message}`); } }
    window.deleteComment = async (commentId) => { if (!confirm("Kommentar löschen?")) return; try { const response = await fetch(`/api/comments/entry/${commentId}`, { method: 'DELETE' }); if (!response.ok) { const result = await response.json(); throw new Error(result.error || 'Fehler beim Löschen.'); } document.querySelector(`[data-comment-id="${commentId}"]`).remove(); const comments = await (await fetch(`/api/comments/${activeProjectForComments}`)).json(); renderComments(comments); updateCommentCountBadge(activeProjectForComments, comments.length); } catch(error) { alert(`Fehler: ${error.message}`); } }
    let mouseDownPos = {x: 0, y: 0};
    unifiedBody.addEventListener('mousedown', e => { handleMouseDown(e); isDraggingFlag = false; mouseDownPos = {x: e.clientX, y: e.clientY}; const onMouseMove = (moveEvent) => { const dx = Math.abs(moveEvent.clientX - mouseDownPos.x); const dy = Math.abs(moveEvent.clientY - mouseDownPos.y); if (dx > 5 || dy > 5) { isDraggingFlag = true; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } }; const onMouseUp = (upEvent) => { if (!isDraggingFlag) { handleTaskClick(upEvent); } document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); });
    unifiedBody.addEventListener('dragstart', handleDragStart); unifiedBody.addEventListener('dragover', handleDragOver); unifiedBody.addEventListener('drop', handleDrop); unifiedBody.addEventListener('dragend', handleDragEnd); unifiedBody.addEventListener('click', e => { handleConnectorClick(e); handleListClick(e); });
    document.addEventListener('click', (e) => { if (!e.target.closest('.comment-actions')) { document.querySelectorAll('.comment-actions-menu').forEach(menu => menu.classList.add('hidden')); } });
    sortBtn.addEventListener('click', handleSortProjects);
    prevWeekBtn.addEventListener('click', () => { currentWeekOffset--; filterAndRender(); });
    currentWeekBtn.addEventListener('click', () => { currentWeekOffset = WEEKS_IN_PAST; filterAndRender(); });
    nextWeekBtn.addEventListener('click', () => { currentWeekOffset++; filterAndRender(); });
    saveBtn.addEventListener('click', () => saveChanges(false));
    sapUpdateBtn.addEventListener('click', handleSapUpdate);
    searchInput.addEventListener('input', filterAndRender);
    document.getElementById('unifiedTableContainer').addEventListener('scroll', updateScrollShadow);
    window.addEventListener('resize', updateScrollShadow);
    saveAssignmentsBtn.addEventListener('click', () => saveManualAssignments(currentModalData.assignedEmployeeIds));
    deleteAssignmentsBtn.addEventListener('click', () => saveManualAssignments([]));
    cancelAssignmentsBtn.addEventListener('click', closeAssignmentModal);
    document.getElementById('saveKitBtn').addEventListener('click', () => {
        saveKitAssignment(document.getElementById('hardwareKitSelect').value || null);
    });
    closeAssignmentModalBtn.addEventListener('click', closeAssignmentModal);
    assignmentModalBackdrop.addEventListener('click', closeAssignmentModal);
    deleteEventBtn.addEventListener('click', handleDeleteEvent);
    closeCommentsButton.addEventListener('click', closeCommentsModal);
    commentsOverlay.addEventListener('click', (e) => { if (e.target === commentsOverlay) closeCommentsModal(); });
    postCommentBtn.addEventListener('click', postNewComment);

function handleTaskClick(e) {
        const taskContainer = e.target.closest('.task-segment-container'); 
        if (!taskContainer || e.target.closest('.resize-handle')) return;

        const taskBar = taskContainer.closest('.task-bar'); 
        const projectId = taskBar.dataset.projectId; 
        const taskId = taskBar.dataset.taskId;

        // ### MULTI-SELECT LOGIK ###
        // Wenn STRG oder CMD (Meta) gedrückt ist, nur die Auswahl umschalten und KEIN Modal öffnen
        if (e.ctrlKey || e.metaKey) {
            toggleTaskSelection(taskId);
            return; 
        }
        // ##########################

        const project = masterProjectDetails[projectId]; 
        const task = findTask(projectId, taskId);

        if (!project || !task || !project.active) return;

        // Wenn es ein einfacher Klick ist (kein Drag, kein Ctrl), und der Task noch nicht ausgewählt war,
        // setzen wir die Auswahl zurück und wählen nur diesen einen Task aus.
        if (!selectedTaskIds.has(taskId)) {
             clearSelection();
             toggleTaskSelection(taskId, true);
        }

        // --- Ab hier Standard-Logik zum Öffnen des Modals ---
        const cell = taskBar.closest('td'); 
        const colIndex = parseInt(cell.dataset.columnIndex, 10);

        const slotDateTime = new Date(masterTimelineStartDate.getTime() + colIndex * 43200000); 
        const slotDate = new Date(Date.UTC(slotDateTime.getUTCFullYear(), slotDateTime.getUTCMonth(), slotDateTime.getUTCDate())); 
        const slotShift = slotDateTime.getUTCHours() < 12 ? 'AM' : 'PM';

        const segmentIndex = colIndex - task.start; 
        const assignedEmployeeIds = (task.assigned_employees && task.assigned_employees[segmentIndex]) ? task.assigned_employees[segmentIndex] : []; 
        const isManual = (task.is_manual && task.is_manual[segmentIndex]) ? task.is_manual[segmentIndex] : false;
        const testCellIdForSlot = (task.test_cell_id && task.test_cell_id[segmentIndex]) ? task.test_cell_id[segmentIndex] : null;

        openAssignmentModal({ 
            project: project, 
            event: task, 
            slotDate: slotDate, 
            slotShift: slotShift, 
            assignedEmployeeIds: [...assignedEmployeeIds], 
            isManual: isManual, 
            testCellId: testCellIdForSlot 
        });
    }

// 1. Klick auf Footer-Zelle -> Modal öffnen & Checkboxen rendern
    unifiedFooter.addEventListener('click', e => {
        const cell = e.target.closest('td.footer-cell-clickable'); 
        if (!cell) return;

        const colIndex = parseInt(cell.dataset.columnIndex, 10); 
        const slotDateTime = new Date(masterTimelineStartDate.getTime() + colIndex * 43200000); 
        const slotDateIso = slotDateTime.toISOString().split('T')[0]; 
        const slotShift = slotDateTime.getUTCHours() < 12 ? 'AM' : 'PM';

        blockerModalTitle.textContent = `Blockaden am ${new Date(slotDateIso).toLocaleDateString('de-DE')} (${slotShift})`; 
        blockerModalContent.innerHTML = '';

        let foundBlockers = false;

        manualAssignments.forEach(ma => {
            if (ma.date_str === slotDateIso && ma.shift === slotShift) {
                foundBlockers = true; 
                const employee = allEmployees.find(emp => emp.id.toLowerCase() === ma.employee_id.toLowerCase()); 
                const employeeInitials = employee ? employee.initials : ma.employee_id;
                const employeeName = employee ? employee.name : 'Unbekannt';

                // Wir speichern alle nötigen Daten direkt im Dataset der Checkbox
                const div = document.createElement('div');
                div.className = 'flex items-center p-3 hover:bg-gray-50 rounded border border-gray-100 transition-colors';
                div.innerHTML = `
                    <label class="flex items-center w-full cursor-pointer gap-3">
                        <input type="checkbox" class="blocker-checkbox h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
                            data-project-code="${ma.project_code}"
                            data-engine-serial="${ma.engine_serial}"
                            data-event-title="${ma.event_title}"
                            data-date-str="${ma.date_str}"
                            data-shift="${ma.shift}"
                            data-employee-id="${ma.employee_id}">
                        <div class="flex-1">
                            <div class="flex items-center gap-2">
                                <span class="inline-flex items-center justify-center h-6 w-6 rounded-full bg-blue-100 text-blue-800 text-xs font-bold">${employeeInitials}</span>
                                <span class="font-medium text-gray-900 text-sm">${employeeName}</span>
                            </div>
                            <div class="text-xs text-gray-500 mt-0.5 pl-8">
                                Geblockt durch: <span class="font-semibold">${ma.event_title}</span> (${ma.project_code})
                            </div>
                        </div>
                    </label>
                `;
                blockerModalContent.appendChild(div);
            }
        });

        if (!foundBlockers) { 
            blockerModalContent.innerHTML = '<p class="text-gray-500 text-center py-4">Keine manuellen Blockaden gefunden.</p>'; 
        }

        // Reset "Select All"
        const selectAllCb = document.getElementById('selectAllBlockers');
        if(selectAllCb) {
            selectAllCb.checked = false;
            selectAllCb.disabled = !foundBlockers;
        }

        assignmentModalBackdrop.style.display = 'block'; 
        blockerModal.style.display = 'flex'; // Flex für Zentrierung
    });

    // 2. "Alle auswählen" Logik
    document.getElementById('selectAllBlockers').addEventListener('change', (e) => {
        document.querySelectorAll('.blocker-checkbox').forEach(cb => cb.checked = e.target.checked);
    });

    // 3. "Ausgewählte löschen" Logik (Gruppen-Verarbeitung)
    document.getElementById('deleteSelectedBlockersBtn').addEventListener('click', async () => {
        const checkedBoxes = document.querySelectorAll('.blocker-checkbox:checked');
        if (checkedBoxes.length === 0) {
            alert("Bitte wählen Sie mindestens einen Eintrag aus.");
            return;
        }

        if (!confirm(`${checkedBoxes.length} Zuweisung(en) wirklich löschen?`)) return;

        // Wir müssen die Löschungen gruppieren, da die API pro "Task-Slot" speichert.
        // Ein Task-Slot ist definiert durch: Project + Engine + Event + Date + Shift.
        // Wenn wir MA aus verschiedenen Events gleichzeitig löschen, müssen wir die API mehrfach aufrufen.

        const tasksToUpdate = {}; // Key: Unique Task String, Value: { slotInfo, idsToDelete: [] }

        checkedBoxes.forEach(cb => {
            const d = cb.dataset;
            // Erstelle einen eindeutigen Schlüssel für das Event
            const taskKey = `${d.projectCode}|${d.engineSerial}|${d.eventTitle}|${d.dateStr}|${d.shift}`;

            if (!tasksToUpdate[taskKey]) {
                tasksToUpdate[taskKey] = {
                    slotInfo: {
                        project_code: d.projectCode,
                        engine_serial: d.engineSerial,
                        event_title: d.eventTitle,
                        date_str: d.dateStr,
                        shift: d.shift
                    },
                    idsToDelete: []
                };
            }
            tasksToUpdate[taskKey].idsToDelete.push(d.employeeId.toLowerCase());
        });

        // Jetzt arbeiten wir die Gruppen nacheinander ab
        let successCount = 0;
        const entries = Object.values(tasksToUpdate);

        for (const taskData of entries) {
            const { slotInfo, idsToDelete } = taskData;

            // Finde alle MA, die aktuell diesem Slot zugewiesen sind (aus dem globalen Speicher)
            // Wir filtern die globalen 'manualAssignments', um alle zu finden, die zu diesem Event gehören
            const currentAssignmentsForEvent = Array.from(manualAssignments.values()).filter(ma => 
                ma.project_code === slotInfo.project_code &&
                ma.engine_serial === slotInfo.engine_serial &&
                ma.event_title === slotInfo.event_title &&
                ma.date_str === slotInfo.date_str &&
                ma.shift === slotInfo.shift
            );

            // Berechne, wer übrig bleibt (Alle aktuellen - Die, die wir löschen wollen)
            const remainingIds = currentAssignmentsForEvent
                .map(ma => ma.employee_id.toLowerCase())
                .filter(id => !idsToDelete.includes(id));

            // API Aufruf für dieses Event
            // Wir nutzen die existierende saveManualAssignments Funktion, die wir vorher definiert haben
            await saveManualAssignments(remainingIds, slotInfo);
            successCount++;
        }

        if (successCount === entries.length) {
            closeBlockerModal();
        }
    });

    function closeBlockerModal() { 
        assignmentModalBackdrop.style.display = 'none'; 
        blockerModal.style.display = 'none'; 
    }

    closeBlockerModalBtn.addEventListener('click', closeBlockerModal);

    settingsBtn.addEventListener('click', () => { assignmentModalBackdrop.style.display = 'block'; settingsModal.style.display = 'block'; });
    function closeSettingsModal() { assignmentModalBackdrop.style.display = 'none'; settingsModal.style.display = 'none'; }
    closeSettingsModalBtn.addEventListener('click', closeSettingsModal);

    initializeTimeline();
});
</script>
</body>
</html>
"""

TIME_MAPPING = {'AM': 0, 'PM': 12}  # 0 = Beginn des Tages, 12 = 12 Uhr mittags

COLOR_MAP = {'default_task_color': 'blue', 'swat_task_color': 'yellow'}

USER_MANAGEMENT_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <link href="https://fonts.googleapis.com/css2?family=Inter%3Awght%40400%3B500%3B600%3B700&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <title>User Management</title>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }

        .role-pill {
            display: inline-flex;
            align-items: center;
            padding: 0.125rem 0.625rem;
            margin: 0.125rem;
            font-size: 0.75rem;
            font-weight: 500;
            border-radius: 9999px;
            border: 1px solid;
        }
        .role-ADMIN { background-color: #fee2e2; color: #991b1b; border-color: #fecaca; }
        .role-PLANNER { background-color: #e0e7ff; color: #312e81; border-color: #c7d2fe; }
        .role-DEFAULT { background-color: #f3f4f6; color: #374151; border-color: #e5e7eb; }

    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">
    {% include 'sidebar.html' %}
    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Benutzerverwaltung</h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">
            <div class="max-w-7xl mx-auto bg-white shadow-lg rounded-lg p-6">
                <div class="mb-8 flex justify-between items-center">
                    <div>
                        <h2 class="text-2xl font-bold text-slate-800">User Management</h2>
                        <p class="text-slate-500 mt-1 text-sm">Benutzer anlegen, bearbeiten und Rollen zuweisen.</p>
                    </div>
                    <button class="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 flex items-center gap-2" id="addUserBtn">
                        <span class="material-icons-outlined text-xl">person_add</span> Neuen Benutzer hinzufügen
                    </button>
                </div>

                <!-- Formulare (werden bei Bedarf eingeblendet) -->
                <div id="formContainer" class="hidden mb-8"></div>
         
                <div class="bg-white rounded-lg overflow-x-auto border border-slate-200">
                    <table class="min-w-full divide-y divide-slate-200">
                        <thead class="bg-slate-50">
                            <tr>
                                <th class="px-6 py-4 text-left text-xs font-semibold text-slate-600 uppercase">Name</th>
                                <th class="px-6 py-4 text-left text-xs font-semibold text-slate-600 uppercase">ID</th>
                                <th class="px-6 py-4 text-left text-xs font-semibold text-slate-600 uppercase">Level</th>
                                <th class="px-6 py-4 text-left text-xs font-semibold text-slate-600 uppercase">Bereiche</th>
                                <th class="px-6 py-4 text-left text-xs font-semibold text-slate-600 uppercase">Rollen</th>
                                <th class="px-6 py-4 text-center text-xs font-semibold text-slate-600 uppercase">MA / Gruppe</th>
                                <th class="px-6 py-4 text-center text-xs font-semibold text-slate-600 uppercase">Aktionen</th>
                            </tr>
                        </thead>
                        <tbody class="bg-white divide-y divide-slate-200" id="userTableBody">
                            {% for user in users %}
                                <tr data-userid="{{ user.id }}"
                                    data-nachname="{{ user.nachname }}"
                                    data-vorname="{{ user.vorname }}"
                                    data-level="{{ user.level or '' }}"
                                    data-roles="{{ user.roles|join(',') }}"
                                    data-units="{{ user.units|join(', ') }}"
                                    data-is-employee="{{ 'true' if user.is_employee else 'false' }}">
                                    <td class="px-6 py-4 whitespace-nowrap"><div class="text-sm font-medium text-slate-900">{{ user.nachname }}, {{ user.vorname }}</div></td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-600">{{ user.id.upper() }}</td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-600">{{ user.level or 'N/A' }}</td>
                                    <td class="px-6 py-4">
                                        <div class="flex flex-wrap gap-1">
                                            {% for unit in user.units %}
                                                <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-slate-100 text-slate-800 border border-slate-200">
                                                    {{ unit }}
                                                </span>
                                            {% else %}
                                                <span class="text-xs text-slate-400 italic">Alle</span>
                                            {% endfor %}
                                        </div>
                                    </td>
                                    <td class="px-6 py-4">
                                        <div class="flex flex-wrap">
                                            {% for role in user.roles %}
                                                <span class="role-pill role-{{ role.split('_')[0] if '_' in role else ('ADMIN' if role == 'ADMIN' else 'DEFAULT') }}">{{ role }}</span>
                                            {% endfor %}
                                        </div>
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-center">
                                        {% if user.is_employee %}
                                            {% set grp = attendance_groups.get(user.id, '') %}
                                            <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 border border-green-200" title="Gruppe wird im Anwesenheitsplan verwaltet">
                                                <span class="material-icons-outlined text-sm">check_circle</span>
                                                {{ grp if grp else '(keine Gruppe)' }}
                                            </span>
                                        {% else %}
                                            <span class="text-xs text-slate-400 italic">–</span>
                                        {% endif %}
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-center">
                                        <button class="text-blue-500 hover:text-blue-700 edit-user-btn mr-2" title="Benutzer bearbeiten"><span class="material-icons-outlined text-lg">edit</span></button>
                                        <button class="text-red-500 hover:text-red-700 delete-user-btn" title="Benutzer löschen"><span class="material-icons-outlined text-lg">delete_outline</span></button>
                                    </td>
                                </tr>
                            {% else %}
                                <tr><td colspan="7" class="text-center py-4 text-slate-500">Keine Benutzer gefunden.</td></tr>
                            {% endfor %}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </main>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
    // --- Sidebar Logik (unverändert) ---
    const sidebar = document.getElementById('sidebar');
    const sidebarToggleBtn = document.getElementById('sidebar-toggle');
    if (sidebar && sidebarToggleBtn) {
        const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
        const applyState = (isCollapsed) => {
            if (isCollapsed) { sidebar.classList.add('is-collapsed'); toggleBtnIcon.textContent = 'menu'; } 
            else { sidebar.classList.remove('is-collapsed'); toggleBtnIcon.textContent = 'chevron_left'; }
        };
        sidebarToggleBtn.addEventListener('click', () => {
            const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
            localStorage.setItem('sidebarCollapsed', isNowCollapsed);
            applyState(isNowCollapsed);
        });
        const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
        applyState(savedState);
    }

    // --- NEUE Benutzerverwaltungslogik ---
    const addUserBtn = document.getElementById('addUserBtn');
    const formContainer = document.getElementById('formContainer');
    const userTableBody = document.getElementById('userTableBody');
    const availableRoles = {{ available_roles | tojson }};

    function generateFormHTML(mode, userData = {}) {
        const isEdit = mode === 'edit';
        const title = isEdit ? 'Benutzer bearbeiten' : 'Neuen Benutzer hinzufügen';
        const submitText = isEdit ? 'Änderungen speichern' : 'Benutzer hinzufügen';
        const formId = isEdit ? 'editUserForm' : 'addUserForm';
        
        // Dynamisch die Checkboxen für die Rollen generieren
        let rolesCheckboxesHTML = '';
        for (const roleName in availableRoles) {
            const description = availableRoles[roleName];
            const isChecked = isEdit && userData.roles && userData.roles.includes(roleName);
            rolesCheckboxesHTML += `
                <label class="flex items-start p-2 rounded-md hover:bg-slate-50 cursor-pointer">
                    <input type="checkbox" name="roles" value="${roleName}" class="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" ${isChecked ? 'checked' : ''}>
                    <div class="ml-3 text-sm">
                        <span class="font-medium text-gray-900">${roleName}</span>
                        <p class="text-gray-500 text-xs">${description}</p>
                    </div>
                </label>
            `;
        }

        return `
            <div class="bg-white rounded-lg p-6 border border-slate-200">
                <h2 class="text-xl font-semibold text-slate-800 mb-4">${title}</h2>
                <form class="space-y-4" id="${formId}">
                    ${isEdit ? `<input type="hidden" id="editUserIdOriginal" name="editUserIdOriginal" value="${userData.id}">` : ''}
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                        <div><label class="block text-sm font-medium text-slate-700">Nachname</label><input class="mt-1 block w-full rounded-md border-gray-300 shadow-sm" name="nachname" value="${userData.nachname || ''}" required></div>
                        <div><label class="block text-sm font-medium text-slate-700">Vorname</label><input class="mt-1 block w-full rounded-md border-gray-300 shadow-sm" name="vorname" value="${userData.vorname || ''}" required></div>
                    </div>
                    <div><label class="block text-sm font-medium text-slate-700">Benutzer-ID</label><input class="mt-1 block w-full rounded-md border-gray-300 shadow-sm ${isEdit ? 'bg-slate-100' : ''}" name="id" value="${userData.id || ''}" ${isEdit ? 'readonly' : ''} required></div>
                    <div><label class="block text-sm font-medium text-slate-700">Level</label><input class="mt-1 block w-full rounded-md border-gray-300 shadow-sm" name="level" value="${userData.level || ''}" placeholder="z.B. Teamleiter, Mechaniker" required></div>
                    <div>
                        <label class="block text-sm font-medium text-slate-700">Zuständige Bereiche (Indizes)</label>
                        <input class="mt-1 block w-full rounded-md border-gray-300 shadow-sm" name="units" value="${userData.units ? userData.units.join(', ') : ''}" placeholder="z.B. 1.1, 5 (Leer = Alle)">
                        <p class="text-xs text-gray-500 mt-1">Kommagetrennt. Schließt Unterbereiche (z.B. 1.1.2) automatisch ein.</p>
                    </div>
                    <div class="border rounded-lg p-4 bg-green-50 border-green-200">
                        <div class="flex items-center justify-between">
                            <div>
                                <label class="text-sm font-medium text-slate-700">Mitarbeiter (Anwesenheit &amp; Qualifikation)</label>
                                <p class="text-xs text-slate-500 mt-0.5">Erstellt automatisch Anwesenheitsslot und Qualifikationseintrag. Gruppe wird im Anwesenheitsplan zugewiesen.</p>
                            </div>
                            <label class="relative inline-flex items-center cursor-pointer ml-4">
                                <input type="checkbox" name="is_employee" value="true" class="sr-only peer" ${(userData.is_employee !== false) ? 'checked' : ''}>
                                <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div>
                            </label>
                        </div>
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-slate-700 mb-2">Rollen</label>
                        <div class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-2 border rounded-lg p-3 bg-slate-50 max-h-48 overflow-y-auto">
                            ${rolesCheckboxesHTML}
                        </div>
                    </div>
                    <div class="flex justify-end space-x-3 pt-2">
                        <button class="px-4 py-2 bg-slate-200 text-slate-700 text-sm font-medium rounded-md hover:bg-slate-300" type="button" onclick="hideForms()">Abbrechen</button>
                        <button class="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700" type="submit">${submitText}</button>
                    </div>
                </form>
            </div>
        `;
    }

    function showForm(mode, userData = {}) {
        formContainer.innerHTML = generateFormHTML(mode, userData);
        formContainer.classList.remove('hidden');
        addUserBtn.classList.add('hidden');
        
        const form = formContainer.querySelector('form');
        form.addEventListener('submit', mode === 'edit' ? handleEditUser : handleAddUser);
    }

    window.hideForms = function() {
        formContainer.classList.add('hidden');
        formContainer.innerHTML = '';
        addUserBtn.classList.remove('hidden');
    }

    addUserBtn.addEventListener('click', () => showForm('add'));

    userTableBody.addEventListener('click', (event) => {
        const editButton = event.target.closest('.edit-user-btn');
        const deleteButton = event.target.closest('.delete-user-btn');

        if (editButton) {
            const row = editButton.closest('tr');
            const unitsStr = row.dataset.units || '';
            const units = unitsStr ? unitsStr.split(',').map(s => s.trim()) : [];

            const userData = {
                id: row.dataset.userid,
                nachname: row.dataset.nachname,
                vorname: row.dataset.vorname,
                level: row.dataset.level,
                roles: row.dataset.roles ? row.dataset.roles.split(',') : [],
                units: units,
                is_employee: row.dataset.isEmployee !== 'false'
            };
            showForm('edit', userData);
        } else if (deleteButton) {
            const row = deleteButton.closest('tr');
            const userId = row.dataset.userid;
            const name = `${row.dataset.nachname}, ${row.dataset.vorname}`;
            const isEmp = row.dataset.isEmployee === 'true';
            const msg = isEmp
                ? `Benutzer "${name}" (${userId.toUpperCase()}) wirklich löschen?\n\n• Login-Account wird entfernt\n• Anwesenheitsslot wird entfernt\n• Qualifikationsdaten bleiben archiviert\n\nAlternative: IS_EMPLOYEE deaktivieren (reversibel).`
                : `Benutzer "${name}" (${userId.toUpperCase()}) wirklich löschen?`;
            if (confirm(msg)) {
                handleDeleteUser(userId);
            }
        }
    });

    async function handleAddUser(event) {
        event.preventDefault();
        const formData = new FormData(event.target);
        const roles = formData.getAll('roles');
        const unitsStr = formData.get('units');
        const units = unitsStr ? unitsStr.split(',').map(s => s.trim()).filter(Boolean) : [];

        const userData = {
            nachname: formData.get('nachname'),
            vorname: formData.get('vorname'),
            id: formData.get('id'),
            level: formData.get('level'),
            roles: roles,
            units: units,
            is_employee: formData.get('is_employee') === 'true',
        };

        try {
            const response = await fetch("{{ url_for('api_add_user') }}", {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(userData)
            });
            const result = await response.json();
            if (response.ok) {
                alert(result.message || 'Benutzer hinzugefügt!');
                location.reload();
            } else { throw new Error(result.error); }
        } catch (err) { alert('Fehler: ' + err.message); }
    }

    async function handleEditUser(event) {
        event.preventDefault();
        const formData = new FormData(event.target);
        const userId = formData.get('editUserIdOriginal');
        const roles = formData.getAll('roles');
        const unitsStr = formData.get('units');
        const units = unitsStr ? unitsStr.split(',').map(s => s.trim()).filter(Boolean) : [];

        const updatedUserData = {
            nachname: formData.get('nachname'),
            vorname: formData.get('vorname'),
            level: formData.get('level'),
            roles: roles,
            units: units,
            is_employee: formData.get('is_employee') === 'true',
        };

        try {
            const response = await fetch(`{{ url_for('api_edit_user', user_id='USER_ID_PLACEHOLDER') }}`.replace('USER_ID_PLACEHOLDER', userId), {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(updatedUserData)
            });
            const result = await response.json();
            if (response.ok) {
                alert(result.message || 'Benutzer aktualisiert!');
                location.reload();
            } else { throw new Error(result.error); }
        } catch (err) { alert('Fehler: ' + err.message); }
    }
    
    async function handleDeleteUser(userId) {
        try {
            const response = await fetch(`{{ url_for('api_delete_user', user_id='USER_ID_PLACEHOLDER') }}`.replace('USER_ID_PLACEHOLDER', userId), { method: 'DELETE' });
            const result = await response.json();
            if (response.ok) {
                alert(result.message || 'Benutzer gelöscht!');
                location.reload();
            } else { throw new Error(result.error); }
        } catch (err) { alert('Fehler: ' + err.message); }
    }
});
</script>
</body></html>
"""

TIMELINE_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Timeline</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter%3Awght%40400%3B500%3B600%3B700&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet"/>
    <style>
        .timeline-table tbody tr { height: 65px; } .timeline-table thead th { border-bottom: 1px solid #e5e7eb; }
        .timeline-table tbody td { border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb; min-width: 45px; width: 45px; max-width: 45px; padding: 0; vertical-align: middle; position: relative; overflow: hidden; }
        .timeline-table tbody td:last-child { border-right: none; } .timeline-table thead th > div:first-child { line-height: 1.5rem; padding-top: 0.25rem; padding-bottom: 0.25rem; }
        .timeline-table thead th div { padding-top: 0.25rem; padding-bottom: 0.25rem; }
        .task-segment-container { width: 100%; height: 100%; display: flex; align-items: center; font-size: 0.75rem; color: white; overflow: hidden; }
        .task-segment-content { flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 2px 4px; overflow: hidden; white-space: nowrap; height: 100%; text-align: center; }
        .task-name-display { font-weight: 600; font-size: 0.65rem;letter-spacing: -0.04em; line-height: 1.1; margin-bottom: 3px; width: 100%; overflow: hidden; text-overflow: ellipsis; }
        .employee-initials-container { display: flex; justify-content: center; align-items: center; gap: 2px; margin-top: 2px; }
        .employee-initial-circle { width: 16px; height: 16px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.3); color: white; display: flex; justify-content: center; align-items: center; font-size: 0.6rem; font-weight: 500; }
        .task-bar { width: 100%; height: 100%; cursor: default; position: relative; } .task-bar:hover .tooltip-text { visibility: visible; opacity: 1; }
        .tooltip-text { visibility: hidden; width: max-content; background-color: black; color: #fff; text-align: center; border-radius: 6px; padding: 5px 8px; position: absolute; z-index: 30; bottom: 125%; left: 50%; transform: translateX(-50%); opacity: 0; transition: opacity 0.3s; font-size: 0.75rem; white-space: pre-line; }
        .weekend-bg { background-color: #f3f4f6; } .holiday-bg { background-color: #eff6ff; } .current-timeslot-highlight { background-color: rgba(239, 68, 68, 0.15) !important; }
        .material-icons-outlined, .material-symbols-outlined, .material-icons { font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; }
        .swat-spacer-row { height: 22px !important; } .swat-spacer-row-cell { height: 22px !important; background-color: #e2e8f0 !important; padding: 0 !important; line-height: 0 !important; font-size: 0 !important; border-top: 1px solid #cbd5e1 !important; border-bottom: 1px solid #cbd5e1 !important; border-left: none !important; border-right: none !important; vertical-align: top !important; }
        .modal-backdrop { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.75); z-index: 40; display: none; }
        .modal { position: fixed; inset: 0; z-index: 50; display: none; align-items: center; justify-content: center; }
        .modal-container { padding: 1rem; box-sizing: border-box; }
        .modal-content { background-color: white; border-radius: 0.75rem; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04); width: 100%; max-width: 60rem; padding: 1.5rem 2rem; position: relative; max-height: calc(100vh - 2rem); overflow: hidden; display: flex; flex-direction: column; box-sizing: border-box; }
        #projectStatusModalContentContainer { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
        .steps-scroll-container { position: relative; }
        .steps-scroll-container::before, .steps-scroll-container::after { content: ''; position: sticky; display: block; height: 1.5rem; pointer-events: none; z-index: 10; left: 0; right: 0; transition: opacity 0.2s; }
        .steps-scroll-container::before { top: 0; background: linear-gradient(to bottom, white, transparent); opacity: 0; }
        .steps-scroll-container::after { bottom: 0; background: linear-gradient(to top, white, transparent); opacity: 1; }
        .steps-scroll-container.shadow-top::before { opacity: 1; }
        .steps-scroll-container.shadow-bottom::after { opacity: 1; }
        .steps-scroll-container:not(.shadow-bottom)::after { opacity: 0; }
        .modal-close-button { position: absolute; top: 1rem; right: 1rem; color: #6b7280; cursor: pointer; } .modal-close-button:hover { color: #374151; }
        .project-step-grid { display: grid; grid-template-columns: auto 1fr; gap: 0 1rem; align-items: start; cursor: pointer; padding: 0.25rem 0.5rem; border-radius: 0.375rem; margin-bottom: 0.125rem; }
        .project-step-grid:hover { background-color: #f0f4f8; }
        .project-step-grid.selected-step { background-color: #e9eff5; }
        .project-step-icon-col { display: flex; flex-direction: column; align-items: center; height: 100%; position: relative; } .project-step-icon-col.first-step { padding-top: 0.25rem; }
        .project-step-icon-connector { width: 2px; flex-grow: 1; margin: 0.25rem 0; }
        .project-step-icon-circle { display: flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; border-radius: 9999px; position:relative; }
        .project-step-icon-circle .step-number { font-size: 0.875rem; font-weight: 600; }
        .project-step-details { padding-bottom: 1rem; }
        #deviationInputArea, #troubleshootingInputArea, #solutionInputArea { border: 1px dashed #cbd5e1; padding: 1rem; margin-top: 1rem; border-radius: 0.375rem; background-color: #f8fafc; }
        .form-select, .form-input { display: block; width: 100%; border-radius: 0.375rem; border-width: 1px; border-color: #cbd5e1; background-color: #fff; padding: 0.75rem; font-size: 0.875rem; line-height: 1.25rem;}
        #stepHistoryContainer { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
        .history-entry { display: flex; align-items: flex-start; gap: 0.75rem; padding: 0.75rem; background-color: #f9fafb; border-radius: 0.375rem; margin-bottom: 0.75rem; }
        .history-entry .material-symbols-outlined { font-size: 1.125rem; color: #6b7280; margin-top: 0.125rem; }

        /* Styles für die einklappbare Sidebar */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator {
            display: none;
        }
        #sidebar.is-collapsed .sidebar-separator {
            display: block;
            height: 1px;
            background-color: #e2e8f0;
            margin: 0.5rem 0.75rem;
        }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <!-- Hauptinhalt -->
    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <div class="p-4 sm:p-6 lg:p-8">
            
            <!-- HEADER BOX (Immer sichtbar) -->
            <div class="bg-white shadow-lg rounded-lg p-4 sm:p-6 mb-6">
                <div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
                    <div>
                        {% set ns = namespace(prev_id=None, prev_name=None, next_id=None, next_name=None) %}
                        {% for i in range(timelines|length) %}
                            {% if timelines[i].id == timeline_id %}
                                {% if i > 0 %}{% set ns.prev_id = timelines[i-1].id %}{% set ns.prev_name = timelines[i-1].name %}{% endif %}
                                {% if i < timelines|length - 1 %}{% set ns.next_id = timelines[i+1].id %}{% set ns.next_name = timelines[i+1].name %}{% endif %}
                            {% endif %}
                        {% endfor %}
                        <div class="flex items-center gap-1" style="line-height: 1;">
                            {% if ns.prev_id %}
                            <a href="{{ url_for('serve_timeline_page', timeline_id=ns.prev_id) }}" title="{{ ns.prev_name }}" class="text-slate-300 hover:text-slate-600 transition-colors flex-shrink-0 inline-flex items-center">
                                <span class="material-icons-outlined" style="font-size: 28px; line-height: 1; display: block;">chevron_left</span>
                            </a>
                            {% else %}
                            <span class="flex-shrink-0 inline-flex" style="width: 28px;"></span>
                            {% endif %}
                            <h1 class="text-3xl font-bold tracking-tight text-slate-900 leading-none">{{ timeline_name }}</h1>
                            {% if ns.next_id %}
                            <a href="{{ url_for('serve_timeline_page', timeline_id=ns.next_id) }}" title="{{ ns.next_name }}" class="text-slate-300 hover:text-slate-600 transition-colors flex-shrink-0 inline-flex items-center">
                                <span class="material-icons-outlined" style="font-size: 28px; line-height: 1; display: block;">chevron_right</span>
                            </a>
                            {% else %}
                            <span class="flex-shrink-0 inline-flex" style="width: 28px;"></span>
                            {% endif %}
                        </div>
                    </div>
                    <div class="flex items-center gap-2">
                        <div class="relative">
                            <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" style="font-size: 20px;">search</span>
                            <input id="projectSearchInput" class="pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-48 md:w-64 text-sm" placeholder="Projekte/Tasks suchen..." type="text"/>
                        </div>
                        <button id="toggleViewBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-slate-200 px-3 py-2 text-sm font-semibold text-slate-700 shadow-sm hover:bg-slate-300" type="button"></button>
                        <button id="toggleArchiveBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-slate-200 px-3 py-2 text-sm font-semibold text-slate-700 shadow-sm hover:bg-slate-300 transition-colors" onclick="toggleArchiveView()">
                            <span class="material-icons-outlined text-[20px] leading-none">inventory_2</span> Archiv
                        </button>
                        <button id="refreshBtn" class="inline-flex items-center gap-x-1.5 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 transition-colors" type="button">
                            <span class="material-icons-outlined text-[20px] leading-none">refresh</span>
                            Aktualisieren
                        </button>

                    </div>
                </div>
            </div>

            <!-- === LIVE VIEW WRAPPER (Zeitleiste & Heute) === -->
            <div id="liveTimelineView">
                <!-- Timeline Box -->
                <div class="bg-white shadow-lg rounded-lg p-4 sm:p-6 mb-8">
                    <div class="overflow-x-auto @container">
                        <div class="align-middle inline-block min-w-full">
                            <div class="flex">
                                <div class="sticky left-0 z-10 bg-white">
                                    <table class="min-w-[280px] divide-y divide-slate-200 border-r border-slate-200">
                                        <thead class="bg-slate-50"><tr><th class="px-4 py-3.5 text-left text-sm font-semibold text-slate-900 h-[65px] align-middle" scope="col">Projekt</th></tr></thead>
                                        <tbody class="divide-y divide-slate-200 bg-white" id="projectListBody"></tbody>
                                    </table>
                                </div>
                                <div class="flex-grow overflow-x-auto relative" id="timelineScrollContainer">
                                    <table class="min-w-full divide-y divide-slate-200 timeline-table">
                                        <thead class="bg-slate-50 sticky top-0 z-10">
                                            <tr id="timelineHeaderDates"></tr>
                                            <tr id="timelineHeaderShifts"></tr>
                                        </thead>
                                        <tbody class="divide-y divide-slate-200 bg-white" id="timelineBody"></tbody>
                                    </table>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                
                <!-- Heute Box -->
                <div class="bg-white p-6 rounded-lg shadow-lg">
                    <div class="flex justify-between items-center mb-6">
                        <h2 class="text-2xl font-bold text-gray-800">Heutige Zusammenfassung <span id="todayDateSummary" class="text-lg font-normal text-gray-500"></span></h2>
                    </div>
                    <div id="todaysSummaryContent"></div>
                </div>      
            </div>

            <!-- === ARCHIVE VIEW WRAPPER (Tabelle) === -->
            <div id="archiveView" class="hidden">
                <div class="bg-white shadow-lg rounded-lg border border-slate-200">
                    <div class="px-6 py-4 border-b border-slate-100 bg-slate-50 flex justify-between items-center rounded-t-lg">
                        <h2 class="text-xl font-bold text-slate-800 flex items-center gap-2">
                            <span class="material-icons-outlined text-indigo-500">inventory_2</span> Abgeschlossene Projekte
                        </h2>
                        <span class="text-xs text-slate-500">Zieht Daten direkt aus dem System-Protokoll</span>
                    </div>
                    <div class="overflow-x-auto custom-scrollbar min-h-[500px]">
                        <table class="w-full text-sm text-left">
                            <thead class="text-xs text-gray-500 uppercase bg-white border-b border-slate-200 sticky top-0 z-10">
                                <tr>
                                    <th class="px-6 py-4 font-bold">Projekt & Typ</th>
                                    <th class="px-6 py-4 font-bold">S/N</th>
                                    <th class="px-6 py-4 font-bold">Kunde</th>
                                    <th class="px-6 py-4 font-bold text-center">Status</th>
                                    <th class="px-6 py-4 font-bold text-center">Deviations</th>
                                    <th class="px-6 py-4 font-bold text-right">Letztes Update</th>
                                    <th class="px-6 py-4 font-bold text-right"></th>
                                </tr>
                            </thead>
                            <tbody id="archiveTableBody" class="divide-y divide-slate-100">
                                <tr><td colspan="5" class="text-center py-8 text-slate-500">Lade Archiv...</td></tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>

        </div>
    </main>
</div>
<div id="modalBackdrop" class="modal-backdrop"></div>
<div id="projectStatusModal" class="modal"> <div class="modal-container"> <div class="modal-content"> <button id="closeModalButton" class="modal-close-button" aria-label="Schließen"> <span class="material-symbols-outlined">close</span> </button> <div id="projectStatusModalContentContainer"> <p class="text-center text-slate-500">Lade Projektdetails...</p> </div> </div> </div> </div>
<script>
    // --- 1. Sidebar Logik ---
    const sidebar = document.getElementById('sidebar');
    const sidebarToggleBtn = document.getElementById('sidebar-toggle');
    if (sidebar && sidebarToggleBtn) {
        const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
        const applyState = (isCollapsed) => {
            if (isCollapsed) {
                sidebar.classList.add('is-collapsed');
                toggleBtnIcon.textContent = 'menu';
            } else {
                sidebar.classList.remove('is-collapsed');
                toggleBtnIcon.textContent = 'chevron_left';
            }
        };
        sidebarToggleBtn.addEventListener('click', () => {
            const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
            localStorage.setItem('sidebarCollapsed', isNowCollapsed);
            applyState(isNowCollapsed);
        });
        const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
        applyState(savedState);
    }

    // --- 2. Globale Variablen & Konfiguration ---
    const timelineId = {{ timeline_id|tojson }};
    const currentUserId = "{{ session.get('user_id', '').lower() }}";
    const userRoles = {{ session.get('roles', []) | tojson }};
    const isObserverOnly = userRoles.length === 1 && userRoles[0] === 'OBSERVER';
    const projectListBody = document.getElementById('projectListBody');
    const timelineHeaderDates = document.getElementById('timelineHeaderDates');
    const timelineHeaderShifts = document.getElementById('timelineHeaderShifts');
    const timelineBody = document.getElementById('timelineBody');
    const timelineScrollContainer = document.getElementById('timelineScrollContainer');
    const todaysSummaryContent = document.getElementById('todaysSummaryContent');
    const todayDateSummarySpan = document.getElementById('todayDateSummary');
    const projectSearchInput = document.getElementById('projectSearchInput');
    const userProfileIcon = document.getElementById('userProfileCircle');

    const monthNames = ["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"];
    const dayNames = ["So","Mo","Di","Mi","Do","Fr","Sa"];
    const dayNamesLong = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];

    let colorMapping = {'default':'blue','test':'green','rig':'purple','de-rig':'indigo','atw':'red','rigging':'sky','de-rigging':'rose','paperwork':'amber'};
    let tailwindColors = ["slate","gray","zinc","neutral","stone","red","orange","amber","yellow","lime","green","emerald","teal","cyan","sky","blue","indigo","violet","purple","fuchsia","pink","rose"];

    let currentDisplayWeeks = 2;
    let toggleViewBtn, refreshBtn;
    let allFetchedProjects = [];
    let allFetchedTodaysSummary = [];
    let freeDaysSet = new Set();
    let currentGlobalTimelineStartDate;

    // ### FIX TEIL 1: Neue globale Variablen für die Suche ###
    let savedAssignments = {}; 
    let savedEmployeeMap = new Map();
    let savedViewStartDate = new Date(); 
    // ########################################################

    // Modal Variablen
    const modalBackdrop = document.getElementById('modalBackdrop');
    const projectStatusModal = document.getElementById('projectStatusModal');
    const closeModalButton = document.getElementById('closeModalButton');
    const projectStatusModalContentContainer = document.getElementById('projectStatusModalContentContainer');

    let currentSelectedStepIdStr = null;
    let currentProjectDetailsForModal = null;
    const LOGGED_IN_USER_DISPLAY_NAME = "{{ current_user_display_name }}";
    let isHistoryVisible = false;
    let isCommentsVisible = false;
    let cachedProjectComments = null;
    let tempSelectedStatusForStep = null;
    let isDetailViewInEditMode = false;
    // --- NEUE ARCHIV VARIABLEN ---
    let isArchiveView = false;
    let allArchivedProjects =[];

    // --- ARCHIV FUNKTIONEN ---
    function toggleArchiveView() {
        isArchiveView = !isArchiveView;
        const liveView = document.getElementById('liveTimelineView');
        const archiveView = document.getElementById('archiveView');
        const toggleBtn = document.getElementById('toggleArchiveBtn');
        const refreshBtn = document.getElementById('refreshBtn');
        const viewToggleBtn = document.getElementById('toggleViewBtn');

        if (isArchiveView) {
            // Zeige Archiv
            liveView.classList.add('hidden');
            archiveView.classList.remove('hidden');
            toggleBtn.innerHTML = '<span class="material-icons-outlined text-base">arrow_back</span> Zurück zur Planung';
            refreshBtn.style.display = 'none';
            viewToggleBtn.style.display = 'none';
            loadArchiveData();
        } else {
            // Zeige Live
            liveView.classList.remove('hidden');
            archiveView.classList.add('hidden');
            toggleBtn.innerHTML = '<span class="material-icons-outlined text-base">inventory_2</span> Archiv';
            refreshBtn.style.display = 'inline-flex';
            viewToggleBtn.style.display = 'inline-flex';
            // Optional: Live-Ansicht Filter wiederherstellen
            const searchTerm = document.getElementById('projectSearchInput').value;
            fetchAndRenderTimeline(searchTerm);
        }
    }

    async function loadArchiveData() {
        const tbody = document.getElementById('archiveTableBody');
        tbody.innerHTML = '<tr><td colspan="5" class="text-center py-12 text-slate-400 flex flex-col items-center"><span class="material-icons-outlined text-4xl mb-2 animate-spin">sync</span> Lade Archivdaten aus dem System...</td></tr>';
        try {
            const res = await fetch(`/api/archive/projects/${timelineId}`);
            if (!res.ok) throw new Error('Netzwerkfehler');
            allArchivedProjects = await res.json();
            renderArchiveTable();
        } catch (e) {
            tbody.innerHTML = `<tr><td colspan="5" class="text-center py-8 text-red-500">Fehler beim Laden: ${e.message}</td></tr>`;
        }
    }

    function renderArchiveTable() {
        const tbody = document.getElementById('archiveTableBody');
        tbody.innerHTML = '';
        const searchTerm = document.getElementById('projectSearchInput').value.toLowerCase();

        const filtered = allArchivedProjects.filter(p =>
            p.name.toLowerCase().includes(searchTerm) ||
            p.engine_serial.toLowerCase().includes(searchTerm) ||
            p.customer.toLowerCase().includes(searchTerm) ||
            p.engine_type.toLowerCase().includes(searchTerm)
        );

        if (filtered.length === 0) {
            tbody.innerHTML = '<tr><td colspan="7" class="text-center py-12 text-slate-400 italic">Keine Projekte gefunden.</td></tr>';
            return;
        }

        const isAdmin = userRoles.includes('ADMIN');

        const active = filtered.filter(p => !p.is_deep_archived);
        const deepArchived = filtered.filter(p => p.is_deep_archived);

        const renderRow = (p) => {
            const tr = document.createElement('tr');
            const archived = p.is_deep_archived;
            tr.className = archived
                ? 'hover:bg-slate-100 cursor-pointer transition-colors opacity-50 bg-slate-50'
                : 'hover:bg-indigo-50/50 cursor-pointer transition-colors group';
            if (archived && p.uuid) tr.onclick = () => { window.location.href = `/project/${p.uuid}`; };
            else if (!archived) tr.onclick = () => openProjectStatusModal(encodeURIComponent(p.name));

            let statusBadge = p.progress_percent === 100
                ? '<span class="px-2 py-0.5 bg-green-100 text-green-700 border border-green-200 rounded-full text-xs font-bold uppercase tracking-wider">Erledigt (100%)</span>'
                : `<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 rounded-full text-xs font-bold uppercase tracking-wider">${p.progress_percent}%</span>`;

            let devBadge = p.deviation_count === 0
                ? `<span class="text-slate-300 text-xs">—</span>`
                : p.open_deviation_count > 0
                    ? `<span class="px-2 py-0.5 bg-red-100 text-red-700 border border-red-200 rounded-full text-xs font-bold" title="${p.open_deviation_count} offen">${p.deviation_count}</span>`
                    : `<span class="px-2 py-0.5 bg-green-100 text-green-700 border border-green-200 rounded-full text-xs font-bold" title="Alle geschlossen">${p.deviation_count}</span>`;

            let actionBtn = '';
            if (isAdmin) {
                if (archived) {
                    actionBtn = `<button onclick="event.stopPropagation(); restoreProject('${p.filename}')" class="inline-flex items-center gap-1 px-2 py-1 bg-blue-50 hover:bg-blue-100 text-blue-700 border border-blue-200 rounded text-xs font-medium transition-colors">
                        <span class="material-icons-outlined text-[13px]">unarchive</span> Wiederherstellen
                    </button>`;
                } else {
                    actionBtn = `<button onclick="event.stopPropagation(); deepArchiveProject('${p.filename}')" class="inline-flex items-center gap-1 px-2 py-1 bg-slate-100 hover:bg-slate-200 text-slate-600 border border-slate-200 rounded text-xs font-medium transition-colors">
                        <span class="material-icons-outlined text-[13px]">archive</span> Archivieren
                    </button>`;
                }
            }

            let nameCell = archived
                ? `<div class="font-bold text-slate-400">${p.name} <span class="ml-1 px-1.5 py-0.5 bg-slate-200 text-slate-500 rounded text-[9px] font-bold uppercase tracking-wide">Archiviert</span></div>
                   <div class="text-[10px] text-slate-300 uppercase tracking-widest mt-0.5">${p.engine_type}</div>`
                : `<div class="font-bold text-indigo-700 group-hover:text-indigo-900">${p.name}</div>
                   <div class="text-[10px] text-slate-400 uppercase tracking-widest mt-0.5">${p.engine_type}</div>`;

            tr.innerHTML = `
                <td class="px-6 py-4">${nameCell}</td>
                <td class="px-6 py-4 ${archived ? 'text-slate-400' : 'text-slate-700'} font-mono font-medium">${p.engine_serial}</td>
                <td class="px-6 py-4 ${archived ? 'text-slate-400' : 'text-slate-600'}">${p.customer}</td>
                <td class="px-6 py-4 text-center">${statusBadge}</td>
                <td class="px-6 py-4 text-center">${devBadge}</td>
                <td class="px-6 py-4 text-right text-slate-500 text-xs">
                    <div class="flex items-center justify-end gap-1">
                        <span class="material-icons-outlined text-[14px] text-slate-300">history</span>
                        ${p.last_updated}
                    </div>
                </td>
                <td class="px-6 py-4 text-right">${actionBtn}</td>
            `;
            tbody.appendChild(tr);
        };

        active.forEach(renderRow);

        if (deepArchived.length > 0) {
            const sep = document.createElement('tr');
            sep.innerHTML = `<td colspan="7" class="px-6 py-2 bg-slate-100 border-y border-slate-200 text-[10px] font-bold text-slate-400 uppercase tracking-widest">
                <span class="flex items-center gap-1.5"><span class="material-icons-outlined text-[13px]">inventory_2</span> Archiviert (${deepArchived.length})</span>
            </td>`;
            tbody.appendChild(sep);
            deepArchived.forEach(renderRow);
        }
    }
    // --- 3. Hilfsfunktionen für Modal & Steps ---
    function findStepById(steps, idStr) {
        return steps.find(s => s.id_str === idStr);
    }
    function getStepChildren(steps, parentIdStr) {
        return steps.filter(s => s.parent_id_str === parentIdStr).sort((a, b) => {
            const aParts = a.id_str.split('.').map(Number);
            const bParts = b.id_str.split('.').map(Number);
            for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
                if (aParts[i] !== bParts[i]) return aParts[i] - bParts[i];
            }
            return aParts.length - bParts.length;
        });
    }
    function generateNewStepId(steps, parentIdStr) {
        const children = getStepChildren(steps, parentIdStr);
        let maxSubIndex = 0;
        children.forEach(child => {
            const parts = child.id_str.split('.');
            const subIndex = parseInt(parts[parts.length - 1], 10);
            if (subIndex > maxSubIndex) {
                maxSubIndex = subIndex;
            }
        });
        return parentIdStr ? `${parentIdStr}.${maxSubIndex + 1}` : `${maxSubIndex + 1}`;
    }

    async function deepArchiveProject(filename) {
        if (!confirm('Projekt ins Archiv verschieben? Es verschwindet dann aus dem Live Status.')) return;
        const res = await fetch('/api/archive/project/deep-archive', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({filename}) });
        if (res.ok) { loadArchiveData(); } else { const d = await res.json(); alert('Fehler: ' + (d.error || 'Unbekannt')); }
    }
    async function restoreProject(filename) {
        if (!confirm('Projekt wiederherstellen? Es erscheint dann wieder im Live Status.')) return;
        const res = await fetch('/api/archive/project/restore', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({filename}) });
        if (res.ok) { loadArchiveData(); } else { const d = await res.json(); alert('Fehler: ' + (d.error || 'Unbekannt')); }
    }

    function closeProjectStatusModal() {
        modalBackdrop.style.display = 'none'; projectStatusModal.style.display = 'none';
        projectStatusModalContentContainer.innerHTML = ''; currentSelectedStepIdStr = null;
        currentProjectDetailsForModal = null; isHistoryVisible = false; isCommentsVisible = false; cachedProjectComments = null; tempSelectedStatusForStep = null;
    }

    async function saveProjectStatusToBackend(projectDetails) {
        if (!projectDetails || !projectDetails.project_code || !projectDetails.engine_serial) {
            console.error("Speichern fehlgeschlagen: project_code oder engine_serial fehlen.", projectDetails);
            return;
        }
        projectDetails.origin_timeline = timelineId;

        const project_id = `${projectDetails.project_code}_${projectDetails.engine_serial}`;
        try {
            const response = await fetch(`/api/project_status/${encodeURIComponent(project_id)}`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(projectDetails)
            });
            if (!response.ok) {
                const errorData = await response.json();
                throw new Error(errorData.error || 'Fehler beim Speichern auf dem Server.');
            }
        } catch (error) {
            console.error("Fehler beim Speichern des Projektstatus:", error);
            alert("Fehler: Änderungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.");
        }
    }

    function deleteStep(stepIdToDelete) {
        if (!confirm("Möchten Sie diesen Schritt und alle untergeordneten Schritte wirklich und endgültig löschen?")) {
            return;
        }
        if (!currentProjectDetailsForModal) return;
        const stepToDelete = findStepById(currentProjectDetailsForModal.steps, stepIdToDelete);
        if (!stepToDelete || !stepToDelete.parent_id_str) {
            alert("Fehler: Hauptschritte können nicht gelöscht werden.");
            return;
        }
        const parentId = stepToDelete.parent_id_str;
        const idsToDelete = [stepIdToDelete];
        const idsToScanForChildren = [stepIdToDelete];

        while (idsToScanForChildren.length > 0) {
            const currentId = idsToScanForChildren.shift();
            const children = getStepChildren(currentProjectDetailsForModal.steps, currentId);
            children.forEach(child => {
                idsToDelete.push(child.id_str);
                idsToScanForChildren.push(child.id_str);
            });
        }
        currentProjectDetailsForModal.steps = currentProjectDetailsForModal.steps.filter(
            step => !idsToDelete.includes(step.id_str)
        );
        currentSelectedStepIdStr = parentId;
        updateProjectProgress(currentProjectDetailsForModal);
        saveProjectStatusToBackend(currentProjectDetailsForModal).then(() => {
            renderModalContent(currentProjectDetailsForModal); 
        });
    }        

    function updateToggleViewButtonText(){ if(toggleViewBtn) toggleViewBtn.textContent = (currentDisplayWeeks === 2) ? "1 Woche anzeigen" : "2 Wochen anzeigen"; }    

    // --- 4. Hauptfunktion zum Laden und Rendern ---
    async function fetchAndRenderTimeline(searchTerm = '') {
        projectListBody.innerHTML = '<tr><td colspan="1">Lade Projekte...</td></tr>';
        timelineHeaderDates.innerHTML = `<th>Lade Tage...</th>`;
        timelineHeaderShifts.innerHTML = `<th>Lade Schichten...</th>`;
        timelineBody.innerHTML = `<tr><td>Lade Timeline...</td></tr>`;
        todaysSummaryContent.innerHTML = '<p class="text-gray-500 col-span-full text-center">Lade Zusammenfassung...</p>';

        const today = new Date();
        todayDateSummarySpan.textContent = `(${dayNamesLong[today.getDay()]}, ${today.getDate()}. ${monthNames[today.getMonth()]} ${today.getFullYear()})`;

        try {
            const [response, fdResp] = await Promise.all([
                fetch(`/api/v2/timeline_data/${timelineId}?view_mode=static`),
                fetch('/api/free-days')
            ]);
            if (!response.ok) {
                throw new Error(`HTTP ${response.status} ${response.statusText}`);
            }
            const data = await response.json();
            if (fdResp.ok) { try { freeDaysSet = new Set(await fdResp.json()); } catch(e){} }
            if (data.error) {
                throw new Error(data.error);
            }

            allFetchedProjects = data.projects || [];
            const timelineConfig = data.timeline_config;
            currentGlobalTimelineStartDate = new Date(timelineConfig.start_date + 'T00:00:00.000Z');

            // Zeitraum berechnen
            const staticViewStartDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - today.getDay() + 1);
            const numDaysToDisplay = currentDisplayWeeks * 7;
            const numColumnsToDisplay = numDaysToDisplay * 2;

            const employeeMapById = new Map((data.employees || []).map(e => [e.id.toLowerCase(), e]));

            // ### FIX TEIL 2: Daten für die Suche zwischenspeichern ###
            savedAssignments = data.calculated_data.assignments || {};
            savedEmployeeMap = employeeMapById;
            savedViewStartDate = staticViewStartDate; // WICHTIG: Das berechnete Datum speichern
            // ########################################################

            renderTimeline(
                filterProjects(allFetchedProjects, searchTerm),
                staticViewStartDate,
                numDaysToDisplay,
                numColumnsToDisplay,
                savedAssignments, 
                savedEmployeeMap 
            );

            const todaysSummaryData = generateTodaysSummary(allFetchedProjects, savedAssignments, savedEmployeeMap);
            allFetchedTodaysSummary = todaysSummaryData; // Auch wichtig für Suche
            renderTodaysSummary(todaysSummaryData, searchTerm);

        } catch (error) {
            projectListBody.innerHTML = `<tr><td colspan="1" class="text-red-500 p-2">Fehler: ${error.message}</td></tr>`;
            timelineBody.innerHTML = `<tr class="h-[65px]"><td class="text-red-500 p-2">Fehler: ${error.message}</td></tr>`;
            todaysSummaryContent.innerHTML = `<p class="text-red-500">Fehler: ${error.message}</p>`;
        }
    }

    // --- 5. Render-Hilfsfunktionen ---
    function filterProjects(projects,term){ 
        if(!term) return projects; 
        const l=term.toLowerCase(); 
        return projects.filter(p=>(p.name.toLowerCase().includes(l))||(p.engine_type&&p.engine_type.toLowerCase().includes(l))||(p.events.some(t=>t.title.toLowerCase().includes(l))));
    }

    function getTaskColor(taskName,projectType="ENGINE"){ 
        if(projectType==="SWAT") return 'yellow'; 
        const l=taskName.toLowerCase().replace(/\s+/g,'-'); 
        if(colorMapping[l]) return colorMapping[l]; 
        let h=0; 
        for(let i=0;i<l.length;i++) h=l.charCodeAt(i)+((h<<5)-h); 
        return tailwindColors[Math.abs(h)%tailwindColors.length];
    }

    function renderTimeline(projectsData, timelineStartDate, numDaysToDisplay, numColumnsToDisplay, assignments, employeeMap) {
        if (!timelineStartDate || !numDaysToDisplay || !numColumnsToDisplay) {
            projectListBody.innerHTML = '<tr><td class="text-red-500 p-2">Konfig.fehler</td></tr>';
            timelineBody.innerHTML = `<tr class="h-[65px]"><td colspan="${numColumnsToDisplay}" class="text-red-500 p-2">Konfig.fehler</td></tr>`;
            return;
        }

        const S = new Date(timelineStartDate);

        // Linke Spalte (Projektliste)
        projectListBody.innerHTML = '';
        projectsData.forEach(p => {
            if (!p.active) return;
            const r = document.createElement('tr');
            r.className = 'h-[65px]';
            const encodedProjectName = encodeURIComponent(p.name);

            const pct = p.progress_percent ?? 0;
            const progressPill = pct === 100
                ? `<span class="flex-shrink-0 px-1.5 py-0.5 bg-green-100 text-green-700 border border-green-200 rounded-full text-[10px] font-semibold whitespace-nowrap">100%</span>`
                : `<span class="flex-shrink-0 px-1.5 py-0.5 bg-slate-100 text-slate-500 border border-slate-200 rounded-full text-[10px] font-semibold whitespace-nowrap">${pct}%</span>`;

            // --- HIER IST DIE ÄNDERUNG ---
            if (isObserverOnly) {
                // Version für OBSERVER (nicht klickbar)
                r.innerHTML = `<td class="px-4 py-3 align-middle">
                                   <div class="flex items-end gap-2">
                                       <div class="min-w-0 flex-1">
                                           <p class="font-medium text-slate-800 truncate" title="${p.name}">${p.name}</p>
                                           <p class="text-xs text-slate-500 truncate" title="${p.engine_type} / ${p.project_code} / ${p.engine_serial} - ${p.customer}">${p.customer} / ${p.engine_serial}</p>
                                       </div>
                                       ${progressPill}
                                   </div>
                               </td>`;
            } else {
                // Version für alle anderen Rollen (klickbar)
                r.innerHTML = `<td class="px-4 py-3 align-middle">
                                   <div class="flex items-end gap-2">
                                       <div onclick="openProjectStatusModal('${encodedProjectName}')" class="cursor-pointer group min-w-0 flex-1">
                                           <p class="font-medium text-slate-800 group-hover:text-blue-600 group-hover:underline truncate" title="${p.name}">${p.name}</p>
                                           <p class="text-xs text-slate-500 truncate" title="${p.engine_type} / ${p.project_code} / ${p.engine_serial} - ${p.customer}">${p.customer} / ${p.engine_serial}</p>
                                       </div>
                                       ${progressPill}
                                   </div>
                               </td>`;
            }
            // --- ENDE DER ÄNDERUNG ---

            projectListBody.appendChild(r);
        });

        // Header
        timelineHeaderDates.innerHTML = '';
        timelineHeaderShifts.innerHTML = '';
        for (let i = 0; i < numDaysToDisplay; i++) {
            const d = new Date(S);
            d.setDate(S.getDate() + i);
            const w = d.getDay() === 0 || d.getDay() === 6;
            const h = !w && freeDaysSet.has(d.toLocaleDateString('sv'));
            const c = w ? ' weekend-bg' : (h ? ' holiday-bg' : '');
            const thD = document.createElement('th');
            thD.className = `px-1 py-0 text-center text-xs font-medium text-slate-600 w-[90px] whitespace-nowrap border-r border-slate-200 last:border-r-0${c}`;
            thD.colSpan = 2;
            thD.innerHTML = `<div class="py-1.5 border-b border-slate-200">${dayNames[d.getDay()]}, ${d.getDate()}. ${monthNames[d.getMonth()]}</div>`;
            timelineHeaderDates.appendChild(thD);
            ['AM', 'PM'].forEach(shift => {
                const thS = document.createElement('th');
                thS.className = `px-1 py-0 text-center text-xs font-medium text-slate-500 w-[45px] whitespace-nowrap border-r border-slate-200 last:border-r-0${c}`;
                thS.innerHTML = `<div class="py-1.5">${shift}</div>`;
                timelineHeaderShifts.appendChild(thS);
            });
        }

        // Body
        timelineBody.innerHTML = '';
        projectsData.forEach(p => {
            if (!p.active) return;
            const row = document.createElement('tr');
            const cells = Array.from({ length: numColumnsToDisplay }, (_, i) => {
                const cell = document.createElement('td');
                const cellDate = new Date(S.getTime() + Math.floor(i / 2) * 86400000);
                if (cellDate.getDay() === 0 || cellDate.getDay() === 6) {
                    cell.classList.add('weekend-bg');
                } else if (freeDaysSet.has(cellDate.toLocaleDateString('sv'))) {
                    cell.classList.add('holiday-bg');
                }
                return cell;
            });

            (p.events || []).forEach(task => {
                const startDt = new Date(task.start_datetime);
                const endDt = new Date(task.end_datetime);
                let currentDt = new Date(startDt);
                let slotIndex = 0;

                while(currentDt < endDt) {
                    const diffDays = Math.floor((currentDt.getTime() - S.getTime()) / 86400000);
                    const colIndex = (diffDays * 2) + (currentDt.getHours() < 12 ? 0 : 1);

                    if (colIndex >= 0 && colIndex < numColumnsToDisplay) {
                        const eventSlotKey = `${task.id || (p.name + '_' + task.title)}_${slotIndex}`;

                        // Sicherer Zugriff, falls assignments undefined ist
                        const assignmentInfo = (assignments && assignments[eventSlotKey]) ? assignments[eventSlotKey] : { employees:[] };
                            const employeeIds = assignmentInfo.employees ||[];
                        
                            // FIX: Wir müssen die Liste für den Tooltip behalten!
                            const employeeInitials = employeeIds.map(id => (employeeMap && employeeMap.get(id.toLowerCase()) || {}).initials || '??');
                        
                            // ID-basierte Logik für die Anzeige im Balken
                            const ec = employeeIds.map(id => {
                                const lowerId = id.toLowerCase();
                                const emp = employeeMap ? employeeMap.get(lowerId) : null;
                                const initial = emp ? emp.initials : '??';
                                
                                if (lowerId === currentUserId) {
                                    return `<div class="employee-initial-circle" style="background-color: #2563eb; opacity: 1; font-weight: bold; font-size: 9px;" title="Ich (${initial})">${initial}</div>`;
                                } else {
                                    return `<div class="employee-initial-circle" title="${initial}"></div>`;
                                }
                            }).join('');
                        let sch = `<div class="task-segment-content"><span class="task-name-display">${task.title}</span><div class="employee-initials-container">${ec || ''}</div></div>`;
                        const tip = `${task.title}\nMitarbeiter: ${employeeInitials.join(', ')||'-'}`;
                        const tdc = getTaskColor(task.title);
                        const sc = `task-segment-container bg-${tdc}-500 rounded-md`;

                        cells[colIndex].innerHTML = `<div class="task-bar"><div class="${sc}">${sch}</div><span class="tooltip-text">${tip}</span></div>`;
                    }
                    currentDt.setHours(currentDt.getHours() + 12);
                    slotIndex++;
                }
            });
            cells.forEach(cell => row.appendChild(cell));
            timelineBody.appendChild(row);
        });

        highlightCurrentTimeslot(S, numDaysToDisplay);
    }

    function generateTodaysSummary(projects, assignments, employeeMap) {
        const today = new Date();
        today.setHours(0, 0, 0, 0); 
        const todayYear = today.getFullYear();
        const todayMonth = today.getMonth();
        const todayDate = today.getDate();
        const todaysTasks = [];

        projects.forEach(p => {
            if (!p.active) return;
            (p.events || []).forEach(task => {
                const startDt = new Date(task.start_datetime);
                const endDt = new Date(task.end_datetime);
                let currentDt = new Date(startDt);
                let slotIndex = 0;

                while (currentDt < endDt) {
                    if (currentDt.getFullYear() === todayYear &&
                        currentDt.getMonth() === todayMonth &&
                        currentDt.getDate() === todayDate) {

                        const eventSlotKey = `${task.id || (p.name + '_' + task.title)}_${slotIndex}`;
                        const assignmentInfo = (assignments && assignments[eventSlotKey]) ? assignments[eventSlotKey] : { employees: [] };
                        const assignedPeople = assignmentInfo.employees.map(id => {
                            const lowerId = id.toLowerCase();
                            const emp = employeeMap ? employeeMap.get(lowerId) : null;
                            return { id: lowerId, initial: emp ? emp.initials : '??' };
                        });
                    
                        todaysTasks.push({
                            task_name: task.title,
                            assigned_people: assignedPeople, // <-- Name der Eigenschaft geändert!
                            original_shift_name: currentDt.getHours() < 12 ? 'AM' : 'PM',
                            project_name: p.name,
                            project_typ: "ENGINE"
                        });
                    }
                    currentDt.setHours(currentDt.getHours() + 12);
                    slotIndex++;
                }
            });
        });

        todaysTasks.sort((a, b) => (a.original_shift_name === 'PM') - (b.original_shift_name === 'PM') || a.project_name.localeCompare(b.project_name));
        return todaysTasks;
    }

    function highlightCurrentTimeslot(startObj,numDays){
        if(!timelineBody)return;
        timelineBody.querySelectorAll('td:not(.swat-spacer-row-cell)').forEach(c=>c.classList.remove('current-timeslot-highlight')); 
        const T=new Date();
        const TDO=new Date(T.getFullYear(),T.getMonth(),T.getDate(),0,0,0,0); 
        const NSO=new Date(startObj.getFullYear(),startObj.getMonth(),startObj.getDate(),0,0,0,0); 
        const DD=Math.floor((TDO.getTime()-NSO.getTime())/(1000*60*60*24));
        if(DD>=0&&DD<numDays){
            let tci=(T.getHours()>=12)?(DD*2)+1:DD*2; 
            const rows=timelineBody.getElementsByTagName('tr'); 
            for(let i=0;i<rows.length;i++){
                if(rows[i].classList.contains('swat-spacer-row'))continue; 
                const cells=rows[i].getElementsByTagName('td'); 
                if(cells[tci]&&!cells[tci].classList.contains('swat-spacer-row-cell')){
                    cells[tci].classList.add('current-timeslot-highlight');
                }
            } 
            if(timelineScrollContainer){
                const cw=45;
                timelineScrollContainer.scrollLeft=Math.max(0,tci*cw-(timelineScrollContainer.offsetWidth/3));
            }
        }
    }

    function renderTodaysSummary(summaryData,searchTerm=''){
        todaysSummaryContent.innerHTML=''; 
        const lST=searchTerm.toLowerCase(); 
        const fS=searchTerm?summaryData.filter(t=>(t.project_name.toLowerCase().includes(lST))||(t.project_typ&&t.project_typ.toLowerCase().includes(lST))||(t.task_name.toLowerCase().includes(lST))):summaryData;
        if(!fS||fS.length===0){
            todaysSummaryContent.className='grid grid-cols-1';
            const m=searchTerm?'Keine passenden Einträge für heute.':'Keine Projekte für heute geplant'; 
            const sm=searchTerm?'Anderen Suchbegriff versuchen.':'Ein ruhiger Tag!';
            todaysSummaryContent.innerHTML=`<div class="bg-gray-50 p-4 rounded-lg shadow-md border-l-4 border-gray-400 flex flex-col items-center justify-center text-center col-span-full"><span class="material-icons-outlined text-gray-400 text-5xl mb-3">event_busy</span><h3 class="text-lg font-semibold text-gray-700 mb-1">${m}</h3><p class="text-sm text-gray-500">${sm}</p></div>`;return;
        }
        todaysSummaryContent.className='grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6'; 
        const amC=document.createElement('div');amC.className='space-y-4';
        const amH=document.createElement('h4');amH.className='text-xl font-semibold text-gray-700 mb-4 border-b border-gray-300 pb-2';amH.textContent='Frühschicht';amC.appendChild(amH); 
        const pmC=document.createElement('div');pmC.className='space-y-4';
        const pmH=document.createElement('h4');pmH.className='text-xl font-semibold text-gray-700 mb-4 border-b border-gray-300 pb-2';pmH.textContent='Spätschicht';pmC.appendChild(pmH); 
        let amE=false,pmE=false;
        fS.forEach(t=>{
            let tcN,tbC,thC,tcbC,otbC,ottC; 
            if(t.project_typ==="SWAT"){tcN='slate';tbC=`border-${tcN}-400`;thC=`text-${tcN}-600`;tcbC=`bg-${tcN}-100`;otbC=`bg-${tcN}-300`;ottC=`text-${tcN}-700`;}
            else{tcN=getTaskColor(t.task_name,t.project_typ);tbC=`border-${tcN}-500`;thC=`text-${tcN}-700`;tcbC=`bg-${tcN}-50`;otbC=`bg-${tcN}-200`;ottC=`text-${tcN}-600`;}
            const assigned = t.assigned_people ||[];
            
            const eiH = assigned.map(p => {
                if (p.id === currentUserId) {
                    return `<span class="inline-flex h-6 w-6 rounded-full ring-2 ring-white bg-blue-600 text-white items-center justify-center text-[10px] font-bold" title="Ich (${p.initial})">${p.initial}</span>`;
                } else {
                    return `<span class="inline-block h-6 w-6 rounded-full ring-2 ring-white bg-gray-300" title="${p.initial}"></span>`;
                }
            }).join('');
            
            const ptd = t.project_typ || 'N/A';
            
            // FIX: Hier 'assigned.length' statt 't.people_initials.length' verwenden!
            const card = `<div class="${tcbC} p-4 rounded-lg shadow-md border-l-4 ${tbC} hover:shadow-lg transition-shadow duration-200"><div class="flex justify-between items-start mb-2"><h3 class="text-lg font-semibold ${thC}">${t.project_name}</h3><span class="text-xs font-medium ${ottC} ${otbC} px-2.5 py-1 rounded-full whitespace-nowrap">${ptd}</span></div><p class="text-sm text-gray-800 mb-3 font-medium">${t.task_name}</p>${assigned.length > 0 ? `<div class="mt-auto"><span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Team:</span><div class="flex -space-x-2 mt-1.5 overflow-hidden">${eiH}</div></div>` : '<div class="mt-auto"><p class="text-xs text-gray-400 italic">Keine MA zugewiesen.</p></div>'}</div>`;
            if(t.original_shift_name==='AM'){amC.insertAdjacentHTML('beforeend',card);amE=true;}
            else if(t.original_shift_name==='PM'){pmC.insertAdjacentHTML('beforeend',card);pmE=true;}
        });
        if(!amE)amC.insertAdjacentHTML('beforeend','<p class="text-sm text-gray-500 italic p-1 bg-gray-50 rounded tc">Keine AM-Tasks.</p>'); 
        if(!pmE)pmC.insertAdjacentHTML('beforeend','<p class="text-sm text-gray-500 italic p-1 bg-gray-50 rounded tc">Keine PM-Tasks.</p>');
        todaysSummaryContent.appendChild(amC);todaysSummaryContent.appendChild(pmC);
    }

    async function openProjectStatusModal(encodedProjectName) {
        // Navigiert direkt zur Dedicated Page — kein Modal mehr
        try {
            const response = await fetch(`/api/project_details/${encodedProjectName}`);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            const data = await response.json();
            if (data.uuid) {
                window.location.href = `/project/${data.uuid}`;
            } else {
                alert('Projektstatus nicht gefunden.');
            }
        } catch (error) {
            alert('Fehler beim Laden: ' + error.message);
        }
    }

    function renderStepDetailSection(project, selectedStepIdStr) {
        const stepData = findStepById(project.steps, selectedStepIdStr);
        if (!stepData) return '<p class="text-slate-500">Bitte einen Schritt auswählen, um Details anzuzeigen.</p>';

        if (tempSelectedStatusForStep === null || currentSelectedStepIdStr !== selectedStepIdStr) {
            tempSelectedStatusForStep = stepData.status;
        }
        const notes = stepData.notes || '';
        const detailViewTitle = stepData.name.replace(/\s*\(\s*\d+(\.\d+)*\s*\)$/, '').trim();
        let lastUpdatedHtml = '';
        if (stepData.last_updated_by && stepData.last_updated_at) {
            lastUpdatedHtml = `<p class="text-slate-500 text-xs mt-1">Zuletzt aktualisiert von ${stepData.last_updated_by} am ${stepData.last_updated_at}</p>`;
        } else if (stepData.status === 'Upcoming') {
            lastUpdatedHtml = `<p class="text-slate-500 text-xs mt-1">Noch nicht zugewiesen oder gestartet.</p>`;
        }

        let hubIncidentDetailHtml = '';
        if (stepData.type === 'deviation' && stepData.deviation_details && stepData.deviation_details.hub_incident_created) {
            const ticketNr = stepData.deviation_details.hub_incident_number || '';
            const incidentTypeRaw = stepData.deviation_details.hub_incident_type || '';
            const incidentTypeLabels = { befund: 'Befundbeauftragung', schuettware: 'Schüttware', bauteil: 'Bauteil' };
            const incidentTypeLabel = incidentTypeLabels[incidentTypeRaw] || '';
            if (ticketNr) {
                hubIncidentDetailHtml = `
                <div class="mt-4 flex flex-col gap-2 bg-blue-50 border border-blue-200 text-blue-800 text-sm font-medium px-4 py-3 rounded-lg">
                    <div class="flex items-center justify-between">
                        <div class="flex items-center gap-2">
                            <span class="material-symbols-outlined" style="font-size: 20px;">confirmation_number</span>
                            <span>${incidentTypeLabel ? incidentTypeLabel + ' — ' : ''}MTU.Hub Ticket: <strong>${ticketNr}</strong></span>
                        </div>
                        <button onclick="editIncidentNumber('${project.name}', '${stepData.id_str}', '${ticketNr}')" class="text-xs text-blue-600 hover:text-blue-900 underline">Ändern</button>
                    </div>
                </div>`;
            } else {
                hubIncidentDetailHtml = `
                <div class="mt-4 bg-blue-50 border border-blue-200 text-blue-800 text-sm px-4 py-3 rounded-lg">
                    <div class="flex items-center gap-2 mb-2">
                        <span class="material-symbols-outlined">open_in_new</span>
                        <span>${incidentTypeLabel ? incidentTypeLabel + ' — ' : ''}Incident wurde eröffnet.</span>
                    </div>
                    <div class="flex items-center gap-2">
                        <input type="text" id="incNumberInput_${stepData.id_str}" placeholder="Ticket-Nr. (z.B. INC...)" class="form-input h-8 text-xs w-48">
                        <button onclick="saveIncidentNumber('${project.name}', '${stepData.id_str}')" class="bg-blue-600 text-white px-3 py-1 rounded text-xs hover:bg-blue-700">Speichern</button>
                    </div>
                </div>`;
            }
        }

        function getEditViewHtml() {
            const bearbeitetDurchHtml = `<label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Bearbeitet durch</span><input type="text" class="form-input rounded-md bg-slate-100 h-10" value="${LOGGED_IN_USER_DISPLAY_NAME}" readonly /></label>`;
            let contextSpecificFieldHtml = '';
            if (stepData.type === 'deviation') {
                const reason = (stepData.deviation_details && stepData.deviation_details.reason) ? stepData.deviation_details.reason : '';
                contextSpecificFieldHtml = `<label class="flex flex-col w-full"><span class="text-sm font-medium text-slate-700 mb-1">Grund der Abweichung</span><input type="text" id="stepDetailReason" class="form-input h-10" value="${reason}" placeholder="z.B. Falsches Teil geliefert"></label>`;
            } else if (stepData.type === 'troubleshooting') {
                const attempt = (stepData.troubleshooting_details && stepData.troubleshooting_details.attempt) ? stepData.troubleshooting_details.attempt : '';
                contextSpecificFieldHtml = `<label class="flex flex-col w-full"><span class="text-sm font-medium text-slate-700 mb-1">Versuch / Maßnahme</span><input type="text" id="stepDetailAttempt" class="form-input h-10" value="${attempt}" placeholder="z.B. Sensor ausgetauscht"></label>`;
            }
            return `<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-end"><label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Status aktualisieren</span><select id="stepStatusSelect" class="form-select rounded-md h-10 p-3 py-0" onchange="tempSelectedStatusForStep = this.value;"><option value="Upcoming" ${tempSelectedStatusForStep === 'Upcoming' ? 'selected' : ''}>Anstehend</option><option value="In Progress" ${tempSelectedStatusForStep === 'In Progress' ? 'selected' : ''}>In Bearbeitung</option><option value="Completed" ${tempSelectedStatusForStep === 'Completed' ? 'selected' : ''}>Abgeschlossen</option><option value="On Hold" ${tempSelectedStatusForStep === 'On Hold' ? 'selected' : ''}>Angehalten</option><option value="N/R" ${tempSelectedStatusForStep === 'N/R' ? 'selected' : ''}>N/R (Nicht benötigt)</option></select></label>${bearbeitetDurchHtml}</div>${contextSpecificFieldHtml}<label class="flex flex-col w-full"><span class="text-sm font-medium text-slate-700 mb-1">Notizen & Beobachtungen</span><textarea id="stepNotesTextarea" class="form-input rounded-md min-h-32" placeholder="Notizen hinzufügen...">${notes}</textarea></label><div class="flex justify-end items-center gap-3"><button type="button" class="px-4 py-2 text-sm font-medium rounded-md text-slate-700 hover:bg-slate-200" onclick="toggleDetailViewEditMode(false)">Abbrechen</button><button class="flex items-center justify-center gap-2 min-w-[140px] h-10 px-4 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg shadow-sm" onclick="saveStepUpdate('${project.name}', '${stepData.id_str}')"><span class="material-symbols-outlined text-base">save</span> Update speichern</button></div>`;
        }

        function getReadOnlyViewHtml() {
            const statusMap = { "Upcoming": { text: "Anstehend", color: "bg-slate-200 text-slate-700" }, "In Progress": { text: "In Bearbeitung", color: "bg-blue-100 text-blue-700" }, "Completed": { text: "Abgeschlossen", color: "bg-green-100 text-green-700" }, "On Hold": { text: "Angehalten", color: "bg-yellow-100 text-yellow-700" }, "N/R": { text: "Nicht benötigt", color: "bg-gray-200 text-gray-600" } };
            const statusInfo = statusMap[stepData.status] || statusMap["Upcoming"];
            let contextSpecificFieldHtml = '';
            if (stepData.type === 'deviation' && stepData.deviation_details && stepData.deviation_details.reason) {
                contextSpecificFieldHtml = `<div class="mt-4"><p class="text-sm font-medium text-slate-700 mb-1">Grund der Abweichung</p><p class="p-3 bg-white border rounded-md text-slate-800">${stepData.deviation_details.reason}</p></div>`;
            } else if (stepData.type === 'troubleshooting' && stepData.troubleshooting_details && stepData.troubleshooting_details.attempt) {
                contextSpecificFieldHtml = `<div class="mt-4"><p class="text-sm font-medium text-slate-700 mb-1">Versuch / Maßnahme</p><p class="p-3 bg-white border rounded-md text-slate-800">${stepData.troubleshooting_details.attempt}</p></div>`;
            }
            return `<div class="flex justify-between items-center"><p class="text-sm font-medium text-slate-700">Status</p><span class="text-sm font-semibold px-3 py-1 rounded-full ${statusInfo.color}">${statusInfo.text}</span></div>${contextSpecificFieldHtml}<div class="mt-4"><p class="text-sm font-medium text-slate-700 mb-1">Notizen & Beobachtungen</p><div class="p-3 bg-white border rounded-md text-slate-800 min-h-[8rem] whitespace-pre-wrap">${notes || '<span class="text-slate-400 italic">Keine Notizen vorhanden.</span>'}</div></div>`;
        }

        let actionButtonsHtml = '';
        if (stepData.type === 'main') { actionButtonsHtml += `<button class="flex items-center justify-center gap-2 h-10 px-4 bg-yellow-100 hover:bg-yellow-200 text-yellow-800 text-sm font-semibold rounded-lg border border-yellow-300" onclick="toggleDeviationInput(true, '${stepData.id_str}')"><span class="material-symbols-outlined text-base">warning</span> Deviation hinzufügen</button>`; } 
        else if (stepData.type === 'deviation') { actionButtonsHtml += `<button class="flex items-center justify-center gap-2 h-10 px-4 bg-orange-100 hover:bg-orange-200 text-orange-800 text-sm font-semibold rounded-lg border border-orange-300" onclick="toggleTroubleshootingInput(true, '${stepData.id_str}')"><span class="material-symbols-outlined text-base">build_circle</span> Troubleshooting</button>`; actionButtonsHtml += `<button class="flex items-center justify-center gap-2 h-10 px-4 bg-green-100 hover:bg-green-200 text-green-800 text-sm font-semibold rounded-lg border border-green-300" onclick="toggleSolutionInput(true, '${stepData.id_str}')"><span class="material-symbols-outlined text-base">task_alt</span> Solution hinzufügen</button>`; } 
        else if (stepData.type === 'troubleshooting') { actionButtonsHtml += `<button class="flex items-center justify-center gap-2 h-10 px-4 bg-green-100 hover:bg-green-200 text-green-800 text-sm font-semibold rounded-lg border border-green-300" onclick="toggleSolutionInput(true, '${stepData.id_str}')"><span class="material-symbols-outlined text-base">task_alt</span> Solution hinzufügen</button>`; }

        return `<div>
            <div class="flex justify-between items-start">
                <div><h2 class="text-slate-900 text-2xl font-bold leading-tight tracking-tight mb-1">${detailViewTitle} (${stepData.id_str})</h2>${lastUpdatedHtml}</div>
                ${!isDetailViewInEditMode ? `<button class="flex items-center justify-center h-10 w-10 text-blue-600 hover:bg-blue-100 rounded-lg" onclick="toggleDetailViewEditMode(true)" title="Bearbeiten"><span class="material-symbols-outlined text-base">edit</span></button>` : ''}
            </div>
            <div class="mt-6 flex flex-col gap-6">
                <div class="bg-slate-50 p-6 rounded-lg border border-slate-200 space-y-6">${isDetailViewInEditMode ? getEditViewHtml() : getReadOnlyViewHtml()}</div>
                ${hubIncidentDetailHtml}
                <div class="mt-2 pt-6 border-t border-slate-200">
                    <h3 class="text-lg font-semibold text-slate-800 mb-4">Nächste Schritte & Problemlösung</h3>
                    <div class="flex justify-start flex-wrap gap-3">${actionButtonsHtml}</div>
                    
                    <!-- DEVIATION INPUT AREA -->
                    <div id="deviationInputArea" class="hidden mt-4">
                        <div class="p-4 rounded-lg bg-yellow-50 border border-yellow-200 space-y-3">
                            <h4 class="text-md font-semibold text-yellow-900">Neue Deviation hinzufügen</h4>
                            <input type="hidden" id="deviationParentStepId" value="" />
                            <label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Grund der Abweichung</span><input type="text" id="deviationReasonInput" class="form-input h-10" placeholder="z.B. Falsches Teil geliefert, Sensor defekt"></label>
                            <label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Weitere Informationen</span><textarea id="deviationInfoTextarea" class="form-input min-h-20" placeholder="Details zur Abweichung..."></textarea></label>
                            
                            <!-- CHECKBOXEN -->
                            <div class="flex flex-col gap-2 mt-3">
                                <div class="flex flex-col gap-1">
                                    <div class="flex items-center">
                                        <input id="createHubIncidentCheckbox" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" onchange="document.getElementById('hubIncidentTypeWrapper').style.display = this.checked ? 'block' : 'none'">
                                        <label for="createHubIncidentCheckbox" class="ml-2 block text-sm font-medium text-gray-800">MTU.Hub Incident eröffnen</label>
                                    </div>
                                    <div id="hubIncidentTypeWrapper" style="display:none;" class="ml-6">
                                        <select id="hubIncidentTypeSelect" class="form-input h-8 text-sm py-0">
                                            <option value="befund">Befundbeauftragung</option>
                                            <option value="schuettware">Schüttware</option>
                                            <option value="bauteil">Bauteil</option>
                                        </select>
                                    </div>
                                </div>
                                <!-- NEU: PPE Checkbox -->
                                <div class="flex items-center">
                                    <input id="checkPartsCheckbox" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" onchange="togglePpeText(this)">
                                    <label for="checkPartsCheckbox" class="ml-2 block text-sm font-medium text-gray-800">PPE involvieren & Teilecheck</label>
                                </div>
                                <!-- Shop Support Checkbox -->
                                <div class="flex items-center">
                                    <input id="shopSupportCheckbox" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" onchange="toggleShopSupportText(this)">
                                    <label for="shopSupportCheckbox" class="ml-2 block text-sm font-medium text-gray-800">Shop Support involvieren</label>
                                </div>
                            </div>

                            <div class="flex justify-start gap-2 pt-2">
                                <button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm" onclick="submitDeviation()">Deviation Speichern</button>
                                <button type="button" class="bg-slate-200 hover:bg-slate-300 text-slate-700 px-4 py-2 rounded-md text-sm" onclick="toggleDeviationInput(false)">Abbrechen</button>
                            </div>
                        </div>
                    </div>
                    
                    <div id="troubleshootingInputArea" class="hidden mt-4"><div class="p-4 rounded-lg bg-orange-50 border border-orange-200 space-y-3"><h4 class="text-md font-semibold text-orange-900">Neues Troubleshooting hinzufügen</h4><input type="hidden" id="troubleshootingParentStepId" value="" /><label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Versuch / Maßnahme</span><input type="text" id="troubleshootingAttemptInput" class="form-input h-10" placeholder="z.B. Sensor ausgetauscht"></label><label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Ergebnis / Beobachtung</span><textarea id="troubleshootingInfoTextarea" class="form-input min-h-20" placeholder="Details zum Versuch..."></textarea></label><div class="flex justify-start gap-2 pt-2"><button class="bg-orange-600 hover:bg-orange-700 text-white px-4 py-2 rounded-md text-sm" onclick="submitTroubleshooting()">Troubleshooting Speichern</button><button type="button" class="bg-slate-200 hover:bg-slate-300 text-slate-700 px-4 py-2 rounded-md text-sm" onclick="toggleTroubleshootingInput(false)">Abbrechen</button></div></div></div>
                    <div id="solutionInputArea" class="hidden mt-4"><div class="p-4 rounded-lg bg-green-50 border border-green-200 space-y-3"><h4 class="text-md font-semibold text-green-900">Neue Solution hinzufügen</h4><input type="hidden" id="solutionParentStepId" value="" /><label class="flex flex-col"><span class="text-sm font-medium text-slate-700 mb-1">Beschreibung der Lösung</span><textarea id="solutionDescriptionTextarea" class="form-input min-h-20" placeholder="Was war die endgültige Lösung des Problems?"></textarea></label><div class="flex justify-start gap-2 pt-2"><button class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm" onclick="submitSolution()">Solution Speichern</button><button type="button" class="bg-slate-200 hover:bg-slate-300 text-slate-700 px-4 py-2 rounded-md text-sm" onclick="toggleSolutionInput(false)">Abbrechen</button></div></div></div>
                </div>
            </div>
        </div>`;
    }

    function saveIncidentNumber(projectName, stepIdStr) {
        const input = document.getElementById(`incNumberInput_${stepIdStr}`);
        const newNumber = input ? input.value.trim() : '';
        if(!newNumber) return; // Nichts tun bei leerer Eingabe
        
        updateIncidentNumberInBackend(projectName, stepIdStr, newNumber);
    }

    function editIncidentNumber(projectName, stepIdStr, currentNumber) {
        // Wir nutzen prompt für den Edit-Fall, um das UI einfach zu halten, 
        // oder wir setzen den State zurück. Einfacher Hack:
        const newNumber = prompt("Ticket-Nummer korrigieren:", currentNumber);
        if (newNumber !== null) {
            updateIncidentNumberInBackend(projectName, stepIdStr, newNumber.trim());
        }
    }

    async function updateIncidentNumberInBackend(projectName, stepIdStr, number) {
        if (!currentProjectDetailsForModal) return;
        const step = findStepById(currentProjectDetailsForModal.steps, stepIdStr);
        
        if (step && step.deviation_details) {
            step.deviation_details.hub_incident_number = number;
            
            // Speichern
            await saveProjectStatusToBackend(currentProjectDetailsForModal);
            
            // Neu rendern
            renderModalContent(currentProjectDetailsForModal);
        }
    }
    function toggleDetailViewEditMode(enableEditing) {
        isDetailViewInEditMode = enableEditing;
        renderModalContent(currentProjectDetailsForModal);
    }
    function selectStepInModal(stepIdStr) {
        currentSelectedStepIdStr = stepIdStr;
        isDetailViewInEditMode = false; 
        tempSelectedStatusForStep = null; 
        if (currentProjectDetailsForModal) {
            renderModalContent(currentProjectDetailsForModal);
            if (isHistoryVisible) {
                const selectedStep = findStepById(currentProjectDetailsForModal.steps, stepIdStr);
                if (selectedStep) renderStepHistory(currentProjectDetailsForModal, selectedStep);
            }
        }
    }

    function saveStepUpdate(projectName, stepIdStr) {
        const newStatus = tempSelectedStatusForStep;
        const notes = document.getElementById('stepNotesTextarea') ? document.getElementById('stepNotesTextarea').value : '';
        if (currentProjectDetailsForModal) {
            const step = findStepById(currentProjectDetailsForModal.steps, stepIdStr);
            if (step) {
                const oldStatus = step.status;
                const oldNotes = step.notes || '';
                let detailsChanged = false;
                if (step.type === 'deviation') {
                    const reasonInput = document.getElementById('stepDetailReason');
                    if (reasonInput) {
                        const newReason = reasonInput.value;
                        if (!step.deviation_details) step.deviation_details = {};
                        if (step.deviation_details.reason !== newReason) {
                            step.deviation_details.reason = newReason;
                            detailsChanged = true;
                        }
                    }
                } else if (step.type === 'troubleshooting') {
                    const attemptInput = document.getElementById('stepDetailAttempt');
                    if (attemptInput) {
                        const newAttempt = attemptInput.value;
                        if (!step.troubleshooting_details) step.troubleshooting_details = {};
                        if (step.troubleshooting_details.attempt !== newAttempt) {
                            step.troubleshooting_details.attempt = newAttempt;
                            detailsChanged = true;
                        }
                    }
                }
                step.notes = notes;
                if (newStatus !== oldStatus || notes !== oldNotes || detailsChanged) { 
                    step.status = newStatus;
                    step.last_updated_by = LOGGED_IN_USER_DISPLAY_NAME || "Benutzer";
                    const now = new Date();
                    step.last_updated_at = now.toLocaleString('de-DE', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'});
                    step.history = step.history || [];
                    let historyAction = `Status: ${newStatus}`;
                    if (notes !== oldNotes) historyAction += ', Notiz aktualisiert';
                    if (detailsChanged) historyAction += ', Details aktualisiert';
                    step.history.push({ action: historyAction, user: LOGGED_IN_USER_DISPLAY_NAME, timestamp: now.toISOString() });
                }
                tempSelectedStatusForStep = newStatus;
                updateProjectProgress(currentProjectDetailsForModal);

                saveProjectStatusToBackend(currentProjectDetailsForModal).then(() => {
                    isDetailViewInEditMode = false; 
                    renderModalContent(currentProjectDetailsForModal); 
                    if (isHistoryVisible) renderStepHistory(currentProjectDetailsForModal, step);
                });
            }
        }
    }
    function updateProjectProgress(project) {
        const mainSteps = project.steps.filter(s => s.type === 'main' && s.parent_id_str === null);
        const relevantSteps = mainSteps.filter(step => step.status !== 'N/R');
        const totalRelevantSteps = relevantSteps.length;
        if (totalRelevantSteps === 0) {
            project.progress_percent = 100;
            return;
        }
        const stepWeight = 100 / totalRelevantSteps;
        let calculatedProgress = 0;
        relevantSteps.forEach(step => {
            if (step.status === 'Completed') {
                calculatedProgress += stepWeight;
            } else if (step.status === 'In Progress') {
                calculatedProgress += stepWeight * 0.5;
            }
        });
        let finalProgress = Math.round(calculatedProgress);
        if (finalProgress >= 100) {
            const allRelevantStepsCompleted = relevantSteps.every(step => step.status === 'Completed');
            if (!allRelevantStepsCompleted) {
                finalProgress = 99; 
            } else {
                finalProgress = 100; 
            }
        }
        project.progress_percent = finalProgress;
    }
    function toggleInputArea(areaId, parentStepIdInputId, show, parentStepIdStr = null) {
        const area = document.getElementById(areaId);
        const parentStepIdInput = document.getElementById(parentStepIdInputId);
        if (area) {
            area.style.display = show ? 'block' : 'none';
            if (show && parentStepIdInput && parentStepIdStr) parentStepIdInput.value = parentStepIdStr;
            if (!show) { area.querySelectorAll('input[type="text"], textarea').forEach(input => input.value = ''); if (parentStepIdInput) parentStepIdInput.value = '';}
        }
        ['deviationInputArea', 'troubleshootingInputArea', 'solutionInputArea'].forEach(id => { if (id !== areaId) { const otherArea = document.getElementById(id); if (otherArea) otherArea.style.display = 'none';}});
    }
    function toggleDeviationInput(show, parentStepIdStr = null) { toggleInputArea('deviationInputArea', 'deviationParentStepId', show, parentStepIdStr); }
    function toggleShopSupportText(checkbox) {
        const textarea = document.getElementById('deviationInfoTextarea');
        if (!textarea) return;
        const shopText = "INFO: Shop Support ist notwendig\\n\\n";
        if (checkbox.checked) {
            if (!textarea.value.startsWith(shopText)) {
                textarea.value = shopText + textarea.value;
            }
        } else {
            if (textarea.value.startsWith(shopText)) {
                textarea.value = textarea.value.replace(shopText, "");
            }
        }
    }
    function togglePpeText(checkbox) {
        const textarea = document.getElementById('deviationInfoTextarea');
        if (!textarea) return;
    
        const ppeText = "INFO: Bitte PPE involvieren und bestellte Teilnummern auf Richtigkeit prüfen lassen.\\n\\n";
    
        if (checkbox.checked) {
            // Text vorne einfügen, falls noch nicht da
            if (!textarea.value.startsWith(ppeText)) {
                textarea.value = ppeText + textarea.value;
            }
        } else {
            // Text entfernen, falls er da ist
            if (textarea.value.startsWith(ppeText)) {
                textarea.value = textarea.value.replace(ppeText, "");
            }
        }
    }
    function toggleTroubleshootingInput(show, parentStepIdStr = null) { toggleInputArea('troubleshootingInputArea', 'troubleshootingParentStepId', show, parentStepIdStr); }
    function toggleSolutionInput(show, parentStepIdStr = null) { toggleInputArea('solutionInputArea', 'solutionParentStepId', show, parentStepIdStr); }

    function addStep(parentStepIdStr, type, namePrefix, details) {
        if (!currentProjectDetailsForModal) return;
        const parentStep = findStepById(currentProjectDetailsForModal.steps, parentStepIdStr);
        if (!parentStep) return;
        const newIdStr = generateNewStepId(currentProjectDetailsForModal.steps, parentStepIdStr);
        const now = new Date(), timestamp = now.toISOString(), displayTimestamp = now.toLocaleString('de-DE', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'});

        const initialNotes = (details && (details.info || details.description)) || '';
        const newStep = { id_str: newIdStr, parent_id_str: parentStepIdStr, name: `${namePrefix} (${newIdStr})`, status: 'In Progress', type: type, notes: initialNotes, last_updated_by: LOGGED_IN_USER_DISPLAY_NAME, last_updated_at: displayTimestamp, is_expanded: true, history: [{ action: `Erstellt als ${type}`, user: LOGGED_IN_USER_DISPLAY_NAME, timestamp: timestamp }]};

        if (type === 'deviation') {
            newStep.deviation_details = details; 
            newStep.name = `${parentStep.name.split(' (')[0]} - Deviation`;
            parentStep.history = parentStep.history || []; 
            parentStep.history.push({ action: `Deviation ${newIdStr} hinzugefügt`, user: LOGGED_IN_USER_DISPLAY_NAME, timestamp: timestamp });
        } else if (type === 'troubleshooting') {
            newStep.troubleshooting_details = details; newStep.name = `Troubleshooting für ${parentStep.id_str}`;
        } else if (type === 'solution') {
            newStep.solution_details = details; newStep.name = `Solution für ${parentStep.id_str}`; newStep.status = 'Completed';
            const deviationStep = parentStep.type === 'deviation' ? parentStep : findStepById(currentProjectDetailsForModal.steps, parentStep.parent_id_str);
            if (deviationStep && deviationStep.type === 'deviation') {
                deviationStep.status = 'Completed'; deviationStep.history = deviationStep.history || []; deviationStep.history.push({ action: `Geschlossen durch Solution ${newIdStr}`, user: LOGGED_IN_USER_DISPLAY_NAME, timestamp: timestamp });
                getStepChildren(currentProjectDetailsForModal.steps, deviationStep.id_str).forEach(child => { if (child.type === 'troubleshooting') { child.status = 'Completed'; child.history = child.history || []; child.history.push({ action: `Geschlossen durch Solution ${newIdStr}`, user: LOGGED_IN_USER_DISPLAY_NAME, timestamp: timestamp });}});
                const mainParentStep = findStepById(currentProjectDetailsForModal.steps, deviationStep.parent_id_str);
                if (mainParentStep) { if (getStepChildren(currentProjectDetailsForModal.steps, mainParentStep.id_str).filter(d => d.type === 'deviation' && d.status !== 'Completed').length === 0) { mainParentStep.status = 'In Progress'; mainParentStep.history = mainParentStep.history || []; mainParentStep.history.push({ action: `Alle Deviations geschlossen`, user: LOGGED_IN_USER_DISPLAY_NAME, timestamp: timestamp });}}}}
        let insertAtIndex = -1, parentIndex = currentProjectDetailsForModal.steps.indexOf(parentStep); if (parentIndex === -1) return;
        let lastDescendantIndex = parentIndex; function findLastDescendantRecursive(currentParentId) { const children = getStepChildren(currentProjectDetailsForModal.steps, currentParentId); if (children.length === 0) return; const lastChild = children[children.length - 1], lastChildIndex = currentProjectDetailsForModal.steps.indexOf(lastChild); if (lastChildIndex > lastDescendantIndex) lastDescendantIndex = lastChildIndex; findLastDescendantRecursive(lastChild.id_str); } findLastDescendantRecursive(parentStepIdStr); insertAtIndex = lastDescendantIndex + 1;
        currentProjectDetailsForModal.steps.splice(insertAtIndex, 0, newStep); parentStep.is_expanded = true; currentSelectedStepIdStr = newStep.id_str; updateProjectProgress(currentProjectDetailsForModal); 

        saveProjectStatusToBackend(currentProjectDetailsForModal).then(() => {
            renderModalContent(currentProjectDetailsForModal);
        });
    }

    function submitDeviation() {
        const parentStepId = document.getElementById('deviationParentStepId').value;
        const reason = document.getElementById('deviationReasonInput').value;
        const info = document.getElementById('deviationInfoTextarea').value;
        if (!parentStepId || !reason) {
            alert("Bitte geben Sie einen Grund für die Deviation an.");
            return;
        }
        const createIncidentCheckbox = document.getElementById('createHubIncidentCheckbox');
        let incidentCreated = false;
        let incidentType = null;
        if (createIncidentCheckbox && createIncidentCheckbox.checked) {
            if (currentProjectDetailsForModal && currentProjectDetailsForModal.project_code) {
                incidentCreated = true;
                const typeSelect = document.getElementById('hubIncidentTypeSelect');
                incidentType = typeSelect ? typeSelect.value : 'befund';
                const incidentUrls = {
                    befund:      'https://mtuhub.service-now.com/esc?id=sc_cat_item&sys_id=205e46492b95ee900186f382ce91bfa7',
                    schuettware: 'https://mtuhub.service-now.com/esc?id=sc_cat_item&sys_id=97e32caf1b06d2503f7b777c8b4bcb6d',
                    bauteil:     'https://mtuhub.service-now.com/esc?id=sc_cat_item&sys_id=cc82b0e71b86d2503f7b777c8b4bcb34'
                };
                const baseUrl = incidentUrls[incidentType];
                const workOrder = currentProjectDetailsForModal.project_code;
                const shortDescription = encodeURIComponent(reason.trim());
                const description = encodeURIComponent(info.trim());
                const hubUrl = `${baseUrl}&wbs=${workOrder}&desc=${description}&sdesc=${shortDescription}`;
                window.open(hubUrl, '_blank');
            } else {
                alert("Fehler: Projekt-Code konnte nicht gefunden werden, um den Hub-Incident zu erstellen.");
            }
        }
        addStep(parentStepId, 'deviation', 'Deviation', { reason, info, hub_incident_created: incidentCreated, hub_incident_type: incidentType });
        toggleDeviationInput(false);
    }

    function submitTroubleshooting() { const parentStepId = document.getElementById('troubleshootingParentStepId').value, attempt = document.getElementById('troubleshootingAttemptInput').value, info = document.getElementById('troubleshootingInfoTextarea').value; if (!parentStepId || !attempt) { alert("Bitte geben Sie eine Maßnahme für das Troubleshooting an."); return; } addStep(parentStepId, 'troubleshooting', 'Troubleshooting', { attempt, info }); toggleTroubleshootingInput(false); }
    function submitSolution() { const parentStepId = document.getElementById('solutionParentStepId').value, description = document.getElementById('solutionDescriptionTextarea').value; if (!parentStepId || !description) { alert("Bitte geben Sie eine Beschreibung für die Solution an."); return; } addStep(parentStepId, 'solution', 'Solution', { description }); toggleSolutionInput(false); }

    function renderStepHistory(project, selectedStep) {
        const historyContainer = document.getElementById('stepHistoryContent'); if (!historyContainer || !selectedStep) return;
        const history = selectedStep.history || []; let historyHtml = '';
        if (history.length > 0) { [...history].reverse().forEach(entry => { const actionText = entry.action || `Status: ${entry.status || 'Unbekannt'}`, user = entry.user || 'System', timestamp = entry.timestamp ? new Date(entry.timestamp).toLocaleString('de-DE', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'}) : 'Unbekannt'; historyHtml += `<div class="history-entry"><span class="material-symbols-outlined">history</span><div><p class="text-slate-700 text-sm font-medium">${actionText}</p><p class="text-slate-500 text-xs">Von ${user} am ${timestamp}</p></div></div>`; });
        } else if (selectedStep.last_updated_by && selectedStep.last_updated_at) { historyHtml = ` <div class="history-entry"> <span class="material-symbols-outlined">history</span> <div> <p class="text-slate-700 text-sm font-medium">Status geändert zu <span class="font-semibold text-blue-600">${selectedStep.status.replace("In Progress", "In Bearbeitung")}</span></p> <p class="text-slate-500 text-xs">Von ${selectedStep.last_updated_by} am ${selectedStep.last_updated_at}</p> </div> </div> `;
        } else { historyHtml = '<p class="text-slate-500 text-sm">Keine Historie für diesen Schritt verfügbar.</p>'; }
        historyContainer.innerHTML = historyHtml;
    }
    function toggleStepHistory() {
        isHistoryVisible = !isHistoryVisible; const historyContainer = document.getElementById('stepHistoryContainer'), historyButtonText = document.getElementById('viewHistoryButtonText'), historyButtonIcon = document.getElementById('viewHistoryButtonIcon');
        if (historyContainer && historyButtonText && historyButtonIcon) { if (isHistoryVisible) { historyContainer.style.display = 'block'; historyButtonText.textContent = 'Hide History'; historyButtonIcon.textContent = 'visibility_off'; if (currentProjectDetailsForModal && currentSelectedStepIdStr) { const selectedStep = findStepById(currentProjectDetailsForModal.steps, currentSelectedStepIdStr); if (selectedStep) renderStepHistory(currentProjectDetailsForModal, selectedStep); } } else { historyContainer.style.display = 'none'; historyButtonText.textContent = 'View History'; historyButtonIcon.textContent = 'history'; } }
    }
    function toggleProjectComments() {
        isCommentsVisible = !isCommentsVisible;
        const container = document.getElementById('projectCommentsContainer');
        const btnText = document.getElementById('toggleCommentsButtonText');
        if (!container || !btnText) return;
        if (isCommentsVisible) {
            container.style.display = 'block';
            if (cachedProjectComments === null && currentProjectDetailsForModal) {
                const projectId = `${currentProjectDetailsForModal.project_code}_${currentProjectDetailsForModal.engine_serial}`;
                renderProjectComments(null);
                fetch(`/api/comments/${encodeURIComponent(projectId)}`)
                    .then(r => r.ok ? r.json() : Promise.reject())
                    .then(comments => {
                        cachedProjectComments = comments;
                        renderProjectComments(comments);
                        btnText.textContent = comments.length > 0 ? `(${comments.length})` : '';
                    })
                    .catch(() => { cachedProjectComments = []; renderProjectComments([]); });
            } else if (cachedProjectComments !== null) {
                renderProjectComments(cachedProjectComments);
                btnText.textContent = cachedProjectComments.length > 0 ? `(${cachedProjectComments.length})` : '';
            }
        } else {
            container.style.display = 'none';
            btnText.textContent = cachedProjectComments && cachedProjectComments.length > 0 ? `(${cachedProjectComments.length})` : '';
        }
    }
    function renderProjectComments(comments) {
        const content = document.getElementById('projectCommentsContent');
        if (!content) return;
        if (comments === null) { content.innerHTML = '<p class="text-slate-400 italic text-sm py-2">Lade Kommentare…</p>'; return; }
        if (!comments.length) { content.innerHTML = '<p class="text-slate-400 italic text-sm py-2">Keine Kommentare vorhanden.</p>'; return; }
        content.innerHTML = comments.map(c => {
            const authorParts = (c.author || '').split(' ');
            const initials = authorParts.map(w => w[0] || '').join('').toUpperCase().slice(0, 2);
            const dateFmt = c.timestamp ? new Date(c.timestamp).toLocaleDateString('de-DE', {day: '2-digit', month: 'short', year: 'numeric'}) : '';
            const text = (c.comment || c.text || '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
            return `<div class="history-entry"><span class="material-symbols-outlined">chat</span><div class="flex-1 min-w-0"><div class="flex items-center gap-2 mb-1"><span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-slate-200 text-slate-600 text-xs font-bold flex-shrink-0">${initials}</span><span class="text-xs font-medium text-slate-700">${c.author || ''}</span><span class="text-xs text-slate-400">${dateFmt}</span></div><p class="text-slate-600 text-sm whitespace-pre-wrap">${text}</p></div></div>`;
        }).join('');
    }
    function toggleStepExpansion(stepIdStr) {
        if (!currentProjectDetailsForModal) return; const step = findStepById(currentProjectDetailsForModal.steps, stepIdStr);
        if (step && (step.type === 'deviation' || getStepChildren(currentProjectDetailsForModal.steps, step.id_str).length > 0)) { step.is_expanded = !step.is_expanded; renderModalContent(currentProjectDetailsForModal); if (currentSelectedStepIdStr === stepIdStr) { const detailSectionContainer = document.querySelector('.lg\\:w-3\\/5'); if (detailSectionContainer) detailSectionContainer.innerHTML = renderStepDetailSection(currentProjectDetailsForModal, currentSelectedStepIdStr);}}
    }
    function findFirstOpenDescendantDeviation(startStepId, allSteps) {
        const children = getStepChildren(allSteps, startStepId);
        for (const child of children) {
            if (child.type === 'deviation' && child.status !== 'Completed' && child.status !== 'N/R') {
                return child; 
            }
            const foundInChildren = findFirstOpenDescendantDeviation(child.id_str, allSteps);
            if (foundInChildren) {
                return foundInChildren; 
            }
        }
        return null; 
    }

    function hasOpenDescendantDeviations(startStepId, allSteps) {
        return findFirstOpenDescendantDeviation(startStepId, allSteps) !== null;
    }
    function renderModalContent(project) {
        const prevScrollTop = document.getElementById('stepsScrollCol')?.scrollTop ?? 0;
        let stepsHtml = ''; const mainSteps = project.steps.filter(s => s.parent_id_str === null).sort((a,b) => parseFloat(a.id_str) - parseFloat(b.id_str));
        function renderStepRecursive(step, level, projectSteps, isLastInLevel) {
            let html = '', statusText = '', statusColorClass = '', stepNameColorClass = 'text-slate-500', connectorAboveColorClass = 'bg-slate-300', connectorBelowColorClass = 'bg-slate-300';
            let isSelectedClass = (step.id_str === currentSelectedStepIdStr) ? 'selected-step' : '';
            const indentMargin = level * 25;

            const children = getStepChildren(projectSteps, step.id_str), hasChildren = children.length > 0;
            let expansionIcon = '';
            if (hasChildren || step.type === 'deviation') {
                expansionIcon = step.is_expanded ? 'remove_circle_outline' : 'add_circle_outline';
            }

            const iconHtmlContent = `<span class="step-number">${step.id_str}</span>`;

            if (step.status === 'Completed') { statusText = 'Abgeschlossen'; statusColorClass = 'bg-green-500 text-white'; stepNameColorClass = 'text-slate-900'; connectorBelowColorClass = 'bg-green-500';}
            else if (step.status === 'In Progress') { statusText = 'In Bearbeitung'; statusColorClass = 'bg-blue-600 text-white ring-4 ring-blue-200'; stepNameColorClass = 'text-blue-700';}
            else if (step.status === 'On Hold') { statusText = 'Angehalten'; statusColorClass = 'bg-yellow-500 text-black'; stepNameColorClass = 'text-yellow-700';}
            else if (step.status === 'N/R') { statusText = 'Nicht benötigt'; statusColorClass = 'bg-slate-400 text-white'; stepNameColorClass = 'text-slate-500 line-through';}
            else { statusText = 'Anstehend'; statusColorClass = 'border-2 border-slate-400 text-slate-400';}

            if (level > 0) { const parentStep = findStepById(projectSteps, step.parent_id_str); if (parentStep && parentStep.status === 'Completed') connectorAboveColorClass = 'bg-green-500'; } else { const stepIndex = mainSteps.indexOf(step); if (stepIndex > 0 && mainSteps[stepIndex-1].status === 'Completed') connectorAboveColorClass = 'bg-green-500';}

            let stepLastUpdatedHtml = '';
            if (step.last_updated_by && step.last_updated_at) stepLastUpdatedHtml = `<p class="text-slate-500 text-xs mt-1">Von ${step.last_updated_by} am ${step.last_updated_at}</p>`;
            else if (step.status === 'Upcoming') stepLastUpdatedHtml = `<p class="text-slate-500 text-xs mt-1">Noch nicht gestartet</p>`;

            let stepDisplayName = step.name.replace(/\s*\(\s*\d+(\.\d+)*\s*\)$/, '').trim();
            let detailTextHtml = '';
            if (step.type === 'deviation' && step.deviation_details && step.deviation_details.reason) {
                stepDisplayName = `${step.name.split(' (')[0]}`;
                detailTextHtml = `<p class="text-sm italic text-slate-600 mt-0.5">${step.deviation_details.reason}</p>`;
            } else if (step.type === 'troubleshooting' && step.troubleshooting_details && step.troubleshooting_details.attempt) {
                stepDisplayName = `${step.name.split(' (')[0]}`;
                detailTextHtml = `<p class="text-sm italic text-slate-600 mt-0.5">${step.troubleshooting_details.attempt}</p>`;
            } else if (step.type === 'solution') {
                stepDisplayName = `${step.name.split(' (')[0]}`;
                detailTextHtml = `<p class="text-sm italic text-slate-600 mt-0.5">Lösung implementiert</p>`;
            }

            let hubIncidentHtml = '';
            if (step.type === 'deviation' && step.deviation_details && step.deviation_details.hub_incident_created) {
                const badgeTypeRaw = step.deviation_details.hub_incident_type || '';
                const badgeTypeLabels = { befund: 'Befundbeauftragung', schuettware: 'Schüttware', bauteil: 'Bauteil' };
                const badgeTypeLabel = badgeTypeLabels[badgeTypeRaw] || 'MTU.Hub Incident';
                hubIncidentHtml = `<p class="text-xs text-blue-700 font-semibold mt-1.5 flex items-center gap-1 bg-blue-100 px-2 py-1 rounded"><span class="material-symbols-outlined" style="font-size: 14px;">open_in_new</span>${badgeTypeLabel}</p>`;
            }

            let downloadIconHtml = '', deleteIconHtml = '', detailPaddingClass = 'pr-2';
            if (step.has_document) {
                detailPaddingClass = 'pr-8';
                const projectIdForUrl = `${project.project_code}_${project.engine_serial}`;
                const downloadLink = `{{ url_for('download_document_route', project_id='PROJECT_ID_PLACEHOLDER', step_name='STEP_NAME_PLACEHOLDER') }}`.replace('PROJECT_ID_PLACEHOLDER', encodeURIComponent(projectIdForUrl)).replace('STEP_NAME_PLACEHOLDER', encodeURIComponent(step.name));
                downloadIconHtml = `<a href="${downloadLink}" class="absolute bottom-2 right-2 text-slate-400 hover:text-blue-600 p-1 rounded-full transition-colors" title="Dokument herunterladen" onclick="event.stopPropagation();"><span class="material-symbols-outlined" style="font-size: 20px;">download</span></a>`;
            }
            if (step.type !== 'main') {
                detailPaddingClass = 'pr-8';
                deleteIconHtml = `<button class="absolute top-2 right-2 text-slate-400 hover:text-red-600 p-1 rounded-full transition-colors" title="Diesen Schritt löschen" onclick="event.stopPropagation(); deleteStep('${step.id_str}');"><span class="material-symbols-outlined" style="font-size: 18px;">delete_outline</span></button>`;
            }
            let blockedByDeviationHtml = '';
            if (step.type === 'main') {
                const firstOpenDeviation = findFirstOpenDescendantDeviation(step.id_str, projectSteps);
                if (firstOpenDeviation) {
                    blockedByDeviationHtml = `<p class="text-xs text-red-600 font-semibold mt-1 flex items-center gap-1"><span class="material-symbols-outlined" style="font-size: 14px;">error</span>Offene Deviation in <a href="#" class="underline" onclick="event.preventDefault(); selectStepInModal('${firstOpenDeviation.id_str}');">${firstOpenDeviation.id_str}</a></p>`;
                }
            }
            const projectStepDetailsHtml = `<div class="project-step-details relative ${detailPaddingClass}">${deleteIconHtml} <p class="text-base font-semibold leading-normal ${stepNameColorClass}">${stepDisplayName}</p>${detailTextHtml}${hubIncidentHtml}<p class="text-sm font-medium leading-normal mt-1 ${step.status === 'Completed' ? 'text-green-600' : (step.status === 'In Progress' ? 'text-blue-600' : (step.status === 'On Hold' ? 'text-yellow-600' : 'text-slate-500'))}">${statusText}</p>${blockedByDeviationHtml} ${stepLastUpdatedHtml}${downloadIconHtml}</div>`;
            const extraGridClasses = (step.type === 'deviation' && step.status !== 'Completed' && step.status !== 'N/R') ? 'bg-red-50 border border-red-200 rounded-md !pl-4' : '';
            html += `<div class="project-step-grid ${isSelectedClass} ${extraGridClasses}" style="margin-left: ${indentMargin}px;" onclick="selectStepInModal('${step.id_str}')"><div class="project-step-icon-col ${level === 0 && projectSteps.indexOf(step) === 0 && mainSteps.indexOf(step) === 0 ? 'first-step' : ''}">${ (level > 0 || mainSteps.indexOf(step) > 0 || (projectSteps.indexOf(step) > 0 && findStepById(projectSteps, step.parent_id_str))) ? `<div class="project-step-icon-connector ${connectorAboveColorClass}" style="height: 0.25rem;"></div>` : ''}<div class="project-step-icon-circle ${statusColorClass}" ${ (hasChildren || step.type === 'deviation') ? `onclick="event.stopPropagation(); toggleStepExpansion('${step.id_str}')"` : ''} style="cursor: ${ (hasChildren || step.type === 'deviation') ? 'pointer' : 'default'};">${expansionIcon ? `<span class="material-symbols-outlined" style="font-size: 16px; position: absolute; top: -6px; right: -6px; background: white; border-radius: 50%; color: #555; line-height:1; padding:1px;">${expansionIcon}</span>` : ''}${iconHtmlContent}</div>${ (!isLastInLevel || (hasChildren && step.is_expanded)) ? `<div class="project-step-icon-connector ${connectorBelowColorClass}"></div>` : ''}</div>${projectStepDetailsHtml}</div>`;
            if (step.is_expanded && hasChildren) {
                children.forEach((child, index) => {
                    html += renderStepRecursive(child, level + 1, projectSteps, index === children.length - 1);
                });
            }
            return html;
        }
        mainSteps.forEach((mainStep, index) => { stepsHtml += renderStepRecursive(mainStep, 0, project.steps, index === mainSteps.length -1); });
        const stepDetailHtml = currentSelectedStepIdStr ? renderStepDetailSection(project, currentSelectedStepIdStr) : '<div class="lg:w-1/2 border-t lg:border-t-0 lg:border-l border-slate-200 lg:pl-8 pt-6 lg:pt-0"><p class="text-slate-500">Bitte einen Schritt auswählen, um Details anzuzeigen.</p></div>';
        const modalHtml = ` <div class="pb-6 border-b border-slate-200"> <div class="flex flex-wrap justify-between items-start gap-x-4 gap-y-1 mb-1"> <h1 class="text-slate-900 tracking-tight text-3xl md:text-4xl font-bold leading-tight"> ${project.name} </h1> <div class="flex items-center gap-2 text-sm text-slate-600"> <span class="material-symbols-outlined text-slate-500 text-lg">calendar_today</span> <span>Fällig bis: ${project.due_date_display || 'N/A'}</span> </div> </div> <div class="flex items-center justify-between text-slate-500 text-base"> <div>${project.engine_serial && project.engine_serial !== "N/A" ? `<span>SN: ${project.engine_serial}</span>` : ''} ${project.engine_serial && project.engine_serial !== "N/A" && project.engine_type ? `<span class="mx-1">|</span>` : ''} <span>Typ: ${project.engine_type || 'N/A'}</span></div> <div class="flex items-center gap-3"> ${project.uuid ? `<a href="/project/${project.uuid}" class="flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 font-semibold" title="Als Vollbild öffnen"><span class="material-symbols-outlined text-base">open_in_new</span><span class="hidden sm:inline">Vollbild</span></a>` : ''} <button id="toggleCommentsButton" onclick="toggleProjectComments()" class="flex items-center gap-1 text-sm text-slate-600 hover:text-slate-800 transition-colors"> <span class="material-symbols-outlined text-slate-500 text-base">chat</span> <span id="toggleCommentsButtonText"></span> </button> </div></div> </div> <div id="projectCommentsContainer" style="display:none;" class="border-b border-slate-200"> <div id="projectCommentsContent" class="space-y-3 max-h-48 overflow-y-auto pr-1 py-4"></div> </div> <div class="py-3"> <div class="flex items-center gap-3"> <span class="text-slate-500 text-xs font-medium shrink-0">Fortschritt</span> <div class="flex-1 rounded-full bg-slate-200 h-1.5 overflow-hidden"> <div class="h-full rounded-full bg-blue-600 transition-all duration-500 ease-out" style="width: ${project.progress_percent}%;"></div> </div> <span class="text-blue-600 text-xs font-semibold shrink-0">${project.progress_percent}%</span> </div> </div> <div class="flex flex-col lg:flex-row gap-8 pt-6" style="flex: 1; overflow: hidden; min-height: 0;"> <div id="stepsScrollCol" class="lg:w-1/2 steps-scroll-container" style="overflow-y: auto; padding-right: 0.5rem;"> <div class="flex justify-between items-center pb-4"> <h2 class="text-slate-900 text-xl md:text-2xl font-bold leading-tight tracking-[-0.015em]">Projektschritte</h2> <button id="viewHistoryButton" onclick="toggleStepHistory()" class="flex items-center gap-1 text-sm text-blue-600 hover:text-blue-700 font-medium"> <span id="viewHistoryButtonIcon" class="material-symbols-outlined text-base">history</span> <span id="viewHistoryButtonText">View History</span> </button> </div> <div class="space-y-0">${stepsHtml}</div> <div id="stepHistoryContainer" class="hidden"> <h3 class="text-slate-800 text-lg font-semibold mb-3 mt-2">Schritt Historie</h3> <div id="stepHistoryContent" class="space-y-3 max-h-60 overflow-y-auto pr-2"></div> </div> </div> <div class="lg:w-1/2 border-t lg:border-t-0 lg:border-l border-slate-200 lg:pl-8 pt-6 lg:pt-0" style="overflow-y: auto;"> ${stepDetailHtml} </div> </div> `;
        projectStatusModalContentContainer.innerHTML = modalHtml; const stepsColNew = document.getElementById('stepsScrollCol'); if (stepsColNew) stepsColNew.scrollTop = prevScrollTop; const historyContainer = document.getElementById('stepHistoryContainer'); if (historyContainer) historyContainer.style.display = isHistoryVisible ? 'block' : 'none'; const commentsContainer = document.getElementById('projectCommentsContainer'); if (commentsContainer) commentsContainer.style.display = isCommentsVisible ? 'block' : 'none'; if (isCommentsVisible && cachedProjectComments !== null) { renderProjectComments(cachedProjectComments); } const commentsBtn = document.getElementById('toggleCommentsButtonText'); if (commentsBtn) { const count = cachedProjectComments ? cachedProjectComments.length : (project.comment_count || 0); commentsBtn.textContent = count > 0 ? `(${count})` : ''; }
        const selectedStepForDetails = findStepById(project.steps, currentSelectedStepIdStr);
        if (selectedStepForDetails) { const devArea = document.getElementById('deviationInputArea'), troubArea = document.getElementById('troubleshootingInputArea'), solArea = document.getElementById('solutionInputArea'); if (devArea && devArea.style.display === 'block' && selectedStepForDetails.type !== 'main') toggleDeviationInput(false); if (troubArea && troubArea.style.display === 'block' && selectedStepForDetails.type !== 'deviation') toggleTroubleshootingInput(false); if (solArea && solArea.style.display === 'block' && (selectedStepForDetails.type !== 'deviation' && selectedStepForDetails.type !== 'troubleshooting')) toggleSolutionInput(false);
        } else { toggleDeviationInput(false); toggleTroubleshootingInput(false); toggleSolutionInput(false); }
        const stepsCol = document.getElementById('stepsScrollCol');
        if (stepsCol) {
            const updateShadows = () => {
                stepsCol.classList.toggle('shadow-top', stepsCol.scrollTop > 4);
                stepsCol.classList.toggle('shadow-bottom', stepsCol.scrollTop + stepsCol.clientHeight < stepsCol.scrollHeight - 4);
            };
            stepsCol.addEventListener('scroll', updateShadows);
            updateShadows();
        }
    }

    window.onload = () => {
        toggleViewBtn = document.getElementById('toggleViewBtn'); refreshBtn = document.getElementById('refreshBtn');
        if (userProfileIcon) userProfileIcon.addEventListener('click', () => { window.location.href = "{{ url_for('serve_my_qualifications_page') }}"; });
        if (toggleViewBtn) {
            toggleViewBtn.addEventListener('click', () => {
                currentDisplayWeeks = (currentDisplayWeeks === 2) ? 1 : 2;
                updateToggleViewButtonText();
                fetchAndRenderTimeline(projectSearchInput.value); 
            });
        }
        if (refreshBtn) refreshBtn.addEventListener('click', () => fetchAndRenderTimeline(projectSearchInput.value));

        // ### FIX TEIL 3: Event-Listener für die Suche korrigieren ###
        if (projectSearchInput) projectSearchInput.addEventListener('input', (event) => { 
                const searchTerm = event.target.value; 
                
                // --- NEU: Prüfen, in welcher Ansicht wir sind ---
                if (isArchiveView) {
                    renderArchiveTable(); // Filtere das Archiv
                } else {
                    // Das ist der alte Code für die Live-Timeline
                    renderTimeline(
                        filterProjects(allFetchedProjects, searchTerm), 
                        savedViewStartDate,
                        currentDisplayWeeks * 7, 
                        currentDisplayWeeks * 14,
                        savedAssignments,
                        savedEmployeeMap
                    ); 
                    renderTodaysSummary(allFetchedTodaysSummary, searchTerm); 
                }
        });

        if (closeModalButton) closeModalButton.addEventListener('click', closeProjectStatusModal);
        if (modalBackdrop) modalBackdrop.addEventListener('click', (event) => { if (event.target === modalBackdrop) closeProjectStatusModal(); });
        document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && projectStatusModal.style.display !== 'none') closeProjectStatusModal(); });
        updateToggleViewButtonText(); 
        fetchAndRenderTimeline();
    };
</script>
</body></html>
"""

PERSONAL_QUALIFICATIONS_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>MBET - Meine Qualifikationen</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .material-icons-outlined, .material-symbols-outlined { font-weight: normal; font-style: normal; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; }

        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center justify-between whitespace-nowrap px-6 py-4 bg-neutral-100">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800"></h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">
            <div class="mx-auto max-w-7xl">
                <div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
                    <h1 class="text-slate-900 text-3xl md:text-4xl font-bold leading-tight tracking-tight">Willkommen, {{ user_full_name }}!</h1>
                    <div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-blue-50 border border-blue-200">
                        <span class="material-symbols-outlined text-blue-600">workspace_premium</span>
                        <p class="text-blue-700 text-sm font-medium">Level: {{ user_level }}</p>
                    </div>
                </div>

                <!-- NEUES GRID-LAYOUT FÜR QUALIFIKATIONEN UND ANWESENHEIT -->
                <div>

                    <!-- Qualifikationen und Trainings -->
                    <div class="space-y-10">
                        <section>
                            <h2 class="text-slate-800 text-2xl font-semibold leading-snug tracking-tight mb-4 pb-2 border-b border-slate-300">Aktuelle Qualifikationen</h2>
                            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                                {% if current_qualifications %}
                                    {% for qual in current_qualifications %}
                                    <div class="bg-white p-6 rounded-lg shadow-md border border-slate-200 hover:shadow-lg transition-shadow">
                                        <div class="flex items-center gap-3 mb-3"><span class="material-symbols-outlined text-2xl text-green-500">verified_user</span><h3 class="text-slate-900 text-lg font-semibold">{{ qual.name }}</h3></div>
                                        <p class="text-slate-600 text-sm mb-1">Abgeschlossen am: {{ qual.end_date or 'N/A' }}</p>
                                        {% if qual.cert_path %}
                                            <a href="{{ url_for('download_certificate_route', employee_id=user_id, filename=qual.cert_path) }}" target="_blank" class="mt-4 inline-flex items-center gap-1 text-sm font-medium text-blue-600 hover:text-blue-700">
                                                Zertifikat ansehen <span class="material-symbols-outlined text-base">arrow_forward</span>
                                            </a>
                                        {% else %}
                                            <p class="mt-4 text-sm text-gray-400 italic">Kein Zertifikat hinterlegt.</p>
                                        {% endif %}
                                    </div>
                                    {% endfor %}
                                {% else %}
                                    <p class="text-slate-600 text-sm col-span-full">Keine aktuellen Qualifikationen für dich hinterlegt.</p>
                                {% endif %}
                            </div>
                        </section>
                        <section>
                            <h2 class="text-slate-800 text-2xl font-semibold leading-snug tracking-tight mb-4 pb-2 border-b border-slate-300">Laufende Trainings</h2>
                            <div class="space-y-4">
                                {% if ongoing_trainings %}
                                    {% for training in ongoing_trainings %}
                                    <div class="bg-white p-6 rounded-lg shadow-md border border-slate-200 hover:shadow-lg transition-shadow">
                                        <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
                                            <div><div class="flex items-center gap-3 mb-2"><span class="material-symbols-outlined text-2xl {% if training.status == 'TT' %}text-red-500{% else %}text-blue-500{% endif %}">model_training</span><h3 class="text-slate-900 text-lg font-semibold">{{ training.name }}</h3></div><p class="text-slate-600 text-sm">Gestartet am: {{ training.start_date or 'N/A' }}</p></div>
                                            <div class="w-full sm:w-auto"><p class="text-slate-800 text-sm font-medium mb-1 text-right">Fortschritt: {{ training.progress }}%</p><div class="rounded-full bg-slate-200 h-2.5 w-full sm:w-40 overflow-hidden"><div class="h-full rounded-full {% if training.status == 'TT' %}bg-red-500{% else %}bg-blue-500{% endif %}" style="width: {{ training.progress }}%;"></div></div></div>
                                        </div>
                                    </div>
                                    {% endfor %}
                                {% else %}
                                    <p class="text-slate-600 text-sm">Keine laufenden Trainings für dich hinterlegt.</p>
                                {% endif %}
                            </div>
                        </section>
                        <section>
                            <h2 class="text-slate-800 text-2xl font-semibold leading-snug tracking-tight mb-4 pb-2 border-b border-slate-300">Geplante Abwesenheiten</h2>
                            {% if vacation_quota_info %}
                            <div class="mb-4 bg-white rounded-lg border border-slate-200 shadow-sm px-5 py-4 flex flex-wrap gap-6 items-center">
                                <div class="text-sm text-slate-500">Urlaubsquote {{ vacation_quota_info.year }}</div>
                                <div class="flex gap-6 text-sm">
                                    <div><span class="font-semibold text-slate-800">{{ vacation_quota_info.quota }}</span> <span class="text-slate-400">Gesamt</span></div>
                                    <div><span class="font-semibold text-slate-600">{{ vacation_quota_info.used }}</span> <span class="text-slate-400">Verbraucht</span></div>
                                    <div><span class="font-semibold text-blue-600">{{ vacation_quota_info.planned }}</span> <span class="text-slate-400">Geplant</span></div>
                                    <div><span class="font-semibold {% if vacation_quota_info.remaining < 0 %}text-red-600{% elif vacation_quota_info.remaining <= 5 %}text-orange-500{% else %}text-green-600{% endif %}">{{ vacation_quota_info.remaining }}</span> <span class="text-slate-400">Verbleibend</span></div>
                                </div>
                                <div class="ml-auto w-40 h-2 rounded-full bg-slate-100 overflow-hidden">
                                    {% set pct_used = [(vacation_quota_info.used * 100 // vacation_quota_info.quota), 100] | min %}
                                    {% set pct_planned = [((vacation_quota_info.used + vacation_quota_info.planned) * 100 // vacation_quota_info.quota), 100] | min %}
                                    <div class="h-full rounded-full bg-slate-300" style="width: {{ pct_planned }}%;">
                                        <div class="h-full rounded-full bg-slate-500" style="width: {% if pct_planned > 0 %}{{ (pct_used * 100 // pct_planned) }}{% else %}0{% endif %}%;"></div>
                                    </div>
                                </div>
                            </div>
                            {% endif %}
                            {% if future_absences %}
                            <ul class="divide-y divide-slate-100 bg-white rounded-lg border border-slate-200 shadow-sm">
                                {% for ab in future_absences %}
                                {% set dot_color = {'green': 'bg-green-400', 'red': 'bg-red-400', 'blue': 'bg-blue-400', 'purple': 'bg-purple-400', 'orange': 'bg-orange-400', 'yellow': 'bg-yellow-400', 'slate': 'bg-slate-400'} %}
                                <li class="flex items-center gap-4 px-5 py-3">
                                    <span class="w-2 h-2 rounded-full flex-shrink-0 {{ dot_color.get(ab.color, 'bg-slate-400') }}"></span>
                                    <span class="text-sm font-medium text-slate-700 w-28 flex-shrink-0">{{ ab.label }}</span>
                                    <span class="text-sm text-slate-500 flex-1">{% if ab.single %}{{ ab.start }}{% else %}{{ ab.start }} – {{ ab.end }}{% endif %}</span>
                                    <span class="text-xs text-slate-400 tabular-nums">{{ ab.days }}&nbsp;{% if ab.days == 1 %}Tag{% else %}Tage{% endif %}</span>
                                </li>
                                {% endfor %}
                            </ul>
                            {% else %}
                            <p class="text-slate-600 text-sm">Keine geplanten Abwesenheiten eingetragen.</p>
                            {% endif %}
                        </section>
                    </div>

                </div>
            </div>
        </div>
    </main>
</div>
<script>
    // --- Logik für die einklappbare Sidebar ---
    document.addEventListener('DOMContentLoaded', () => {
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    toggleBtnIcon.textContent = 'chevron_left';
                }
            };
            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
        }
    });
</script>
</body>
</html>
"""

TEST_CELL_MANAGEMENT_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Testzellen Verwaltung</title>
    <link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?display=swap&family=Inter:wght@400;500;600;700&family=Noto+Sans:wght@400;500;700;900">
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
<style>
    body { font-family: 'Inter', "Noto Sans", sans-serif; }
    .timeline-table tbody tr { height: 65px; }
    .timeline-table thead th { border-bottom: 1px solid #e5e7eb; }
    .timeline-table tbody td { border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb; min-width: 45px; width: 45px; max-width: 45px; padding: 2px; vertical-align: middle; position: relative; }
    .timeline-table tbody td:last-child { border-right: none; }
    .weekend-bg { background-color: #f3f4f6; }
    .holiday-bg { background-color: #eff6ff; }
    .current-timeslot-highlight { background-color: rgba(239, 68, 68, 0.1) !important; }
    .material-icons-outlined { font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; }
    .modal { display: none; }
    .modal.is-open { display: flex; }
    .event-type-btn.bg-blue-600 { background-color: #2563eb !important; color: white !important; }
    .event-type-btn.bg-blue-600:hover { background-color: #1d4ed8 !important; }
    .cell-card { transition: transform 0.2s ease, box-shadow 0.2s ease; }
    .cell-card:hover { transform: translateY(-4px); box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); }
    @keyframes breathe { 0%, 100% { transform: scale(1); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); } 50% { transform: scale(1.02); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); } }
    .cell-card.operational-pulse { animation: breathe 3s ease-in-out infinite; }
    .cell-card.operational-pulse:hover { animation: none; }
    .analyse-toggle-btn.active {
        background-color: #e0f2fe; /* war #eef2ff (indigo-100) -> jetzt sky-100 */
        color: #0369a1;            /* war #4338ca (indigo-700) -> jetzt sky-700 */
        font-weight: 600;
    }
    #sidebar {
        transition: width 0.3s ease-in-out;
    }
    #sidebar.is-collapsed {
        width: 5rem; /* 80px */
    }
    #sidebar.is-collapsed .sidebar-label,
    #sidebar.is-collapsed .sidebar-title,
    #sidebar.is-collapsed .user-info {
        display: none;
    }
    #sidebar.is-collapsed .sidebar-link,
    #sidebar.is-collapsed .sidebar-header,
    #sidebar.is-collapsed .user-profile {
        justify-content: center;
    }
    #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
    #sidebar.is-collapsed .sidebar-header .material-icons-outlined {
        margin-right: 0;
    }
    .sidebar-label {
        transition: opacity 0.2s ease-in-out;
    }
    .sidebar-link.active {
        background-color: #e5e7eb; /* war #eef2ff (indigo-100) -> jetzt sky-100 */
        color: #0c4a6e;            /* war #4338ca (indigo-700) -> jetzt sky-700 */
        font-weight: 600;
    }
    .sidebar-link.active .material-icons-outlined {
        color: #0c4a6e; /* war #4f46e5 (indigo-600) -> jetzt sky-600 */
    }
    .sidebar-separator {
        display: none;
    }
    #sidebar.is-collapsed .sidebar-separator {
        display: block;
        height: 1px;
        background-color: #e2e8f0;
        margin: 0.5rem 0.75rem;
    }
    .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
    .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
</style>
</head>
<body class="bg-white">

<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

<!-- Hauptinhalt -->
    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Testzellen-Cockpit</h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">

            <!-- Oberer Teil: Button und Karten -->
            <div class="mb-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
                <div>
                    <!-- Titel wurde entfernt, wie gewünscht -->
                </div>
                <!-- NEUE BOX / WRAPPER FÜR DEN BUTTON -->
                <div>
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) %}
                    <button id="newTestCellBtn" class="flex-shrink-0 h-12 w-12 flex items-center justify-center rounded-full bg-sky-900 text-white shadow-sm ring-0 ring-inset ring-slate-200 hover:bg-slate-200 transition-colors" title="Neue Testzelle erstellen">
                        <span class="material-icons-outlined">add</span>
                    </button>
                    {% endif %}
                </div>
            </div>
            <div id="cellCardsContainer" class="mb-24 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
                <!-- Inhalt wird per JS geladen -->
            </div>

            <!-- Steuerungsleiste -->
            <div class="mb-6 flex items-center justify-between">
                <nav id="viewTabs" class="-mb-px flex space-x-8 border-b border-slate-200 w-full">
                    <button data-view="timeline" class="tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-blue-600 text-blue-600">Timeline</button>
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) %}
                    <button data-view="analyse" class="tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Analyse</button>
                    {% endif %}
                </nav>
                <div id="timelineNav" class="flex items-center gap-3 flex-shrink-0 pb-3 ml-6">
                    <button id="jumpToTodayBtn" class="rounded-full bg-white px-4 py-3 text-sm font-semibold text-slate-700 shadow-sm ring-1 ring-inset ring-slate-200 hover:bg-slate-50" title="Zur aktuellen Woche springen">
                        Heute
                    </button>
                    <div class="flex items-center rounded-full ring-1 ring-slate-200 bg-white shadow-sm">
                        <button id="prevWeekBtn" class="p-2 text-slate-500 hover:bg-slate-100 rounded-l-full disabled:opacity-50">
                            <span class="material-icons-outlined">chevron_left</span>
                        </button>
                        <span id="timelineRangeLabel" class="text-sm font-medium text-slate-700 px-4 w-32 text-center border-l border-r border-slate-200">
                            2 Wochen
                        </span>
                        <button id="nextWeekBtn" class="p-2 text-slate-500 hover:bg-slate-100 rounded-r-full disabled:opacity-50">
                            <span class="material-icons-outlined">chevron_right</span>
                        </button>
                    </div>
                </div>
            </div>

            <!-- Unterer Teil: Timeline oder Analyse (jetzt ohne Kachel) -->
            <div id="timelineContainer" class="bg-white rounded-lg">
                <!-- Inhalt wird per JS geladen -->
            </div>
            <div id="analyseContainer" style="display: none;" class="bg-white rounded-lg p-6">
                <div class="mb-6">
                    <h2 class="text-2xl font-bold text-slate-900 mb-2">Auslastungsanalyse 2026</h2>
                    <p class="text-sm text-slate-600">Übersicht der Auslastung basierend auf allen Schichten des aktuellen Kalenderjahres.</p>
                </div>
                <div class="mb-6">
                    <div class="inline-flex rounded-md shadow-sm bg-slate-100 p-1">
                        <button id="analyseViewOverviewBtn" type="button" class="analyse-toggle-btn active rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Übersicht</button>
                        <button id="analyseViewTrendBtn" type="button" class="analyse-toggle-btn rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Verlauf</button>
                        <button id="analyseViewEngineBtn" type="button" class="analyse-toggle-btn rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Engine</button>
                    </div>
                </div>
                <div id="analyseChartsContainer" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
            </div>
        </div>
    </main>
</div>
<!-- Test Cell Modal (Scrollbar Fix) -->
<div id="testCellModal" class="modal fixed inset-0 z-50 items-center justify-center bg-gray-600 bg-opacity-75 p-4 hidden">
    <!-- WICHTIG: flex flex-col und max-h-[90vh] hinzugefügt -->
    <div class="relative w-full max-w-lg transform rounded-lg bg-white shadow-xl overflow-hidden flex flex-col max-h-[90vh]">
        
        <!-- Header (Fixiert) -->
        <div class="flex items-center justify-between p-6 border-b border-gray-200 flex-shrink-0 bg-white z-10">
            <h3 id="testCellModalTitle" class="text-xl font-semibold text-gray-900"></h3>
            <div class="flex items-center space-x-2">
                <button id="editTestCellBtn" type="button" class="p-2 rounded-md hover:bg-gray-100 text-blue-600" title="Bearbeiten">
                    <span class="material-icons-outlined">edit</span>
                </button>
                <button id="deleteTestCellBtnHeader" type="button" class="p-2 rounded-md hover:bg-gray-100 text-red-500" title="Löschen">
                    <span class="material-icons-outlined">delete</span>
                </button>
                <button id="closeTestCellModalBtn" class="p-2 rounded-md hover:bg-gray-100 text-gray-400 hover:text-gray-500">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
        </div>

        <!-- Body (Scrollbar) -->
        <!-- WICHTIG: overflow-y-auto und flex-grow hinzugefügt -->
        <div class="p-6 space-y-6 overflow-y-auto custom-scrollbar flex-grow">
            <input type="hidden" id="originalTestCellId">
            <div>
                <label for="testCellIdInput" class="block text-sm font-medium text-gray-900">Testzellen ID<span class="text-red-500">*</span></label>
                <input type="text" id="testCellIdInput" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="z.B. 1, A, Zelle-01">
            </div>
            <div>
                <label for="testCellNameInput" class="block text-sm font-medium text-gray-900">Testzellen Name<span class="text-red-500">*</span></label>
                <input type="text" id="testCellNameInput" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="z.B. Prüfstand 4.1">
            </div>
            
            <!-- Engine Types -->
            <div>
                <label class="block text-sm font-medium text-gray-900">Kompatible Triebwerkstypen</label>
                <div id="engineTypesContainer" class="mt-2 space-y-2"></div>
                <button id="addEngineTypeBtn" class="mt-2 text-sm font-medium text-blue-600 hover:text-blue-500" type="button">+ Weiteren Typ hinzufügen</button>
            </div>

            <!-- NEU: Abhängigkeiten (Phase 2) -->
            <div class="pt-4 border-t border-gray-100">
                <label class="block text-sm font-medium text-gray-900">Abhängige Betriebseinheiten (Infrastruktur)</label>
                <p class="text-xs text-gray-500 mb-2">Wenn eine dieser Anlagen gesperrt ist (Unit Blocker), wird diese Testzelle automatisch gesperrt.</p>
                
                <div id="requiredUnitsContainer" class="mt-2 space-y-2"></div>
                
                <div class="flex gap-2 mt-2">
                    <input list="modalFacilityUnitList" id="addRequiredUnitInput" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="Anlage suchen (z.B. 1.1)...">
                    <button id="btnAddRequiredUnit" type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">
                        Hinzufügen
                    </button>
                </div>
                <datalist id="modalFacilityUnitList"></datalist>
            </div>
        </div>

        <!-- Footer (Fixiert) -->
        <div class="flex justify-end items-center p-6 bg-gray-50 border-t border-gray-200 rounded-b-lg flex-shrink-0 z-10">
            <button id="cancelTestCellModalBtn" type="button" class="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 text-gray-900 hover:bg-gray-100 mr-3">Abbrechen</button>
            <button id="saveTestCellBtn" type="button" class="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"></button>
        </div>
    </div>
</div>

<div id="eventModal" class="modal fixed inset-0 z-50 items-center justify-center bg-gray-600 bg-opacity-75 p-4"><div class="relative w-full max-w-lg transform rounded-lg bg-white shadow-xl overflow-hidden"><div class="flex items-center justify-between p-6 border-b border-gray-200"><h3 id="eventModalTitle" class="text-xl font-semibold text-gray-900"></h3><div class="flex items-center space-x-2"><button id="editEventBtn" type="button" class="p-2 rounded-md hover:bg-gray-100 text-blue-600" title="Bearbeiten"><span class="material-icons-outlined">edit</span></button><button id="deleteEventBtn" type="button" class="p-2 rounded-md hover:bg-gray-100 text-red-500" title="Löschen"><span class="material-icons-outlined">delete</span></button><button id="closeEventModalBtn" class="p-2 rounded-md hover:bg-gray-100 text-gray-400 hover:text-gray-500"><span class="material-icons-outlined">close</span></button></div></div><div class="p-6 space-y-6"><input type="hidden" id="eventModalEventId"><input type="hidden" id="eventModalCellId"><div><label class="block text-sm font-medium text-gray-900 mb-2">Typ</label><div id="eventTypeButtons" class="flex space-x-2"><button data-type="Maintenance" class="event-type-btn px-4 py-2 text-sm font-medium rounded-md bg-gray-100 text-gray-900 hover:bg-gray-200">Wartung</button><button data-type="Blocked" class="event-type-btn px-4 py-2 text-sm font-medium rounded-md bg-gray-100 text-gray-900 hover:bg-gray-200">Blockade</button></div></div><div><label for="eventTitleInput" class="block text-sm font-medium text-gray-900">Titel<span class="text-red-500">*</span></label><input type="text" id="eventTitleInput" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"></div><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div><label for="eventStartDateInput" class="block text-sm font-medium text-gray-900">Start</label><div class="mt-1 relative rounded-md shadow-sm"><input type="date" id="eventStartDateInput" class="block w-full pr-20 rounded-md border-gray-300 focus:border-blue-500 focus:ring-blue-500 sm:text-sm"><div class="absolute inset-y-0 right-0 flex items-center"><select id="eventStartShiftSelect" class="h-full rounded-md border-transparent bg-transparent py-0 pl-2 pr-7 text-gray-500 focus:border-blue-500 focus:ring-blue-500 sm:text-sm"><option>AM</option><option>PM</option></select></div></div></div><div><label for="eventEndDateInput" class="block text-sm font-medium text-gray-900">Ende</label><div class="mt-1 relative rounded-md shadow-sm"><input type="date" id="eventEndDateInput" class="block w-full pr-20 rounded-md border-gray-300 focus:border-blue-500 focus:ring-blue-500 sm:text-sm"><div class="absolute inset-y-0 right-0 flex items-center"><select id="eventEndShiftSelect" class="h-full rounded-md border-transparent bg-transparent py-0 pl-2 pr-7 text-gray-500 focus:border-blue-500 focus:ring-blue-500 sm:text-sm"><option>AM</option><option>PM</option></select></div></div></div></div><div><label for="eventDescriptionInput" class="block text-sm font-medium text-gray-900">Beschreibung <span class="text-gray-500">(optional)</span></label><textarea id="eventDescriptionInput" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" rows="4"></textarea></div>
<div id="trackingInfoContainer" class="pt-4 border-t border-gray-200 space-y-2 text-sm">
    <p class="font-medium text-gray-700">Tracking-Informationen:</p>
    <div class="grid grid-cols-2 gap-4">
        <div>
            <span class="block text-xs font-medium text-gray-500">Erstellt von:</span>
            <span id="eventCreatedBy" class="block text-gray-800">N/A</span>
        </div>
        <div>
            <span class="block text-xs font-medium text-gray-500">Erstellt am:</span>
            <span id="eventCreatedAt" class="block text-gray-800">N/A</span>
        </div>
    </div>
    <div class="grid grid-cols-2 gap-4">
        <div>
            <span class="block text-xs font-medium text-gray-500">Zuletzt geändert von:</span>
            <span id="eventModifiedBy" class="block text-gray-800">N/A</span>
        </div>
        <div>
            <span class="block text-xs font-medium text-gray-500">Zuletzt geändert am:</span>
            <span id="eventModifiedAt" class="block text-gray-800">N/A</span>
        </div>
    </div>
</div>
</div><div class="flex justify-end items-center p-6 bg-gray-50 border-t border-gray-200 rounded-b-lg"><button id="cancelEventModalBtn" type="button" class="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 text-gray-900 hover:bg-gray-100 mr-3">Abbrechen</button><button id="saveEventBtn" type="button" class="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"></button></div></div></div>

<script>
document.addEventListener('DOMContentLoaded', () => {

    const sidebar = document.getElementById('sidebar');
    const sidebarToggleBtn = document.getElementById('sidebar-toggle');

    if (sidebar && sidebarToggleBtn) {
        const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
        const applyState = (isCollapsed) => {
            if (isCollapsed) {
                sidebar.classList.add('is-collapsed');
                toggleBtnIcon.textContent = 'menu';
            } else {
                sidebar.classList.remove('is-collapsed');
                toggleBtnIcon.textContent = 'chevron_left';
            }
        };
        sidebarToggleBtn.addEventListener('click', () => {
            const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
            localStorage.setItem('sidebarCollapsed', isNowCollapsed);
            applyState(isNowCollapsed);
        });
        const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
        applyState(savedState);
    }

    let allTestCells = [], timelineStartDate = getStartOfWeek(new Date()), saveTimeout = null, isEditMode = false, currentEditingEvent = null, currentEditingCell = null, currentView = 'timeline',
        isModalInEditMode = false, currentAnalyseView = 'overview';
    let freeDaysSet = new Set();

    const CURRENT_USER_DISPLAY_NAME = "{{ current_user_display_name }}";
    
    let chartInstances = {};
  
    const userRoles = {{ session.get('roles', []) | tojson }};
    const canManageCellMasterData = userRoles.includes('ADMIN') || userRoles.includes('PLANNER');
    const canManageCellEvents = userRoles.includes('ADMIN') || userRoles.includes('PLANNER') || userRoles.includes('TESTENGINEER') || userRoles.includes('MAINTENANCEMANAGER') || userRoles.includes('TESTCELLINSPECTOR');

    const dom = {
        cellCardsContainer: document.getElementById('cellCardsContainer'), timelineContainer: document.getElementById('timelineContainer'), analyseContainer: document.getElementById('analyseContainer'), analyseChartsContainer: document.getElementById('analyseChartsContainer'), viewTabs: document.getElementById('viewTabs'), newTestCellBtn: document.getElementById('newTestCellBtn'), prevWeekBtn: document.getElementById('prevWeekBtn'), nextWeekBtn: document.getElementById('nextWeekBtn'), jumpToTodayBtn: document.getElementById('jumpToTodayBtn'), 
        analyseViewOverviewBtn: document.getElementById('analyseViewOverviewBtn'), analyseViewTrendBtn: document.getElementById('analyseViewTrendBtn'), analyseViewEngineBtn: document.getElementById('analyseViewEngineBtn'),
        testCellModal: document.getElementById('testCellModal'), testCellModalTitle: document.getElementById('testCellModalTitle'), closeTestCellModalBtn: document.getElementById('closeTestCellModalBtn'), cancelTestCellModalBtn: document.getElementById('cancelTestCellModalBtn'), saveTestCellBtn: document.getElementById('saveTestCellBtn'), deleteTestCellBtnHeader: document.getElementById('deleteTestCellBtnHeader'), addEngineTypeBtn: document.getElementById('addEngineTypeBtn'), originalTestCellIdInput: document.getElementById('originalTestCellId'), testCellIdInput: document.getElementById('testCellIdInput'), testCellNameInput: document.getElementById('testCellNameInput'), editTestCellBtn: document.getElementById('editTestCellBtn'), engineTypesContainer: document.getElementById('engineTypesContainer'), 
        eventModal: document.getElementById('eventModal'), eventModalTitle: document.getElementById('eventModalTitle'), closeEventModalBtn: document.getElementById('closeEventModalBtn'), cancelEventModalBtn: document.getElementById('cancelEventModalBtn'), saveEventBtn: document.getElementById('saveEventBtn'), deleteEventBtn: document.getElementById('deleteEventBtn'), eventTypeButtons: document.getElementById('eventTypeButtons'), eventModalEventId: document.getElementById('eventModalEventId'), eventModalCellId: document.getElementById('eventModalCellId'), eventTitleInput: document.getElementById('eventTitleInput'), eventStartDateInput: document.getElementById('eventStartDateInput'), eventStartShiftSelect: document.getElementById('eventStartShiftSelect'), eventEndDateInput: document.getElementById('eventEndDateInput'), eventEndShiftSelect: document.getElementById('eventEndShiftSelect'), eventDescriptionInput: document.getElementById('eventDescriptionInput'), editEventBtn: document.getElementById('editEventBtn'),
        trackingInfoContainer: document.getElementById('trackingInfoContainer'),
        eventCreatedBy: document.getElementById('eventCreatedBy'),
        eventCreatedAt: document.getElementById('eventCreatedAt'),
        eventModifiedBy: document.getElementById('eventModifiedBy'),
        eventModifiedAt: document.getElementById('eventModifiedAt')
    };

    function getStartOfWeek(date) { const d = new Date(date); const day = d.getUTCDay(); const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1); return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), diff)); }
    function parseUTCDate(dateString) { return new Date(dateString + 'T00:00:00Z'); }
    function toLocalDateString(date) { if (!date) return ''; const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }

    async function loadData() {
        try {
            const [response, fdResp] = await Promise.all([
                fetch("/api/analytics/test_cells"),
                fetch("/api/free-days")
            ]);
            if (!response.ok) throw new Error('Netzwerkfehler');
            allTestCells = await response.json();
            if (fdResp.ok) { try { freeDaysSet = new Set(await fdResp.json()); } catch(e){} }
            renderAll();
        } catch (error) {
            console.error("Fehler beim Laden der Daten:", error);
            dom.cellCardsContainer.innerHTML = '<p class="col-span-full text-red-500">Fehler beim Laden.</p>';
        }
    }
    function saveData() { 
        if (saveTimeout) clearTimeout(saveTimeout); 
        saveTimeout = setTimeout(async () => { 
            try { 
                await fetch("/api/test_cells", { 
                    method: 'POST', 
                    headers: { 'Content-Type': 'application/json' }, 
                    body: JSON.stringify(allTestCells) 
                }); 
            } catch (error) { 
                console.error('Fehler beim Speichern:', error); 
                alert('Fehler: Änderungen konnten nicht gespeichert werden.'); 
            } 
        }, 500); 
    }
    
    function renderAll() { renderCellCards(); if (currentView === 'timeline') { renderTimelineView(); } else if (currentView === 'analyse') { renderAnalyseView(); } }
    function switchView(view) { currentView = view; document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('border-blue-600', btn.dataset.view === view); btn.classList.toggle('text-blue-600', btn.dataset.view === view); btn.classList.toggle('border-transparent', btn.dataset.view !== view); btn.classList.toggle('text-slate-500', btn.dataset.view !== view); }); dom.timelineContainer.style.display = view === 'timeline' ? 'block' : 'none'; dom.analyseContainer.style.display = view === 'analyse' ? 'block' : 'none'; document.getElementById('timelineNav').style.display = view === 'timeline' ? 'flex' : 'none'; if (view === 'timeline') renderTimelineView(); else renderAnalyseView(); }
    function getCurrentStatus(cell) { 
        const now = new Date(); 
        const todayStr = toLocalDateString(now); 
        const currentShift = now.getHours() < 12 ? 'AM' : 'PM'; 
        
        // Sortierung: Blocked > Maintenance > Operational
        // Wir suchen das "schlimmste" aktive Event
        const priority = { 'Blocked': 3, 'Maintenance': 2, 'Operational': 1 };
        
        let activeEvent = null;
        let maxPrio = 0;
    
        cell.events.forEach(e => { 
            const startOk = (e.start_date < todayStr) || (e.start_date === todayStr && e.start_shift <= currentShift); 
            
            let endOk = false; 
            if (e.type === 'Operational') { 
                endOk = (e.end_date > todayStr) || (e.end_date === todayStr && e.end_shift > currentShift); 
            } else { 
                endOk = (e.end_date > todayStr) || (e.end_date === todayStr && e.end_shift >= currentShift); 
            } 
            
            if (startOk && endOk) {
                const currentPrio = priority[e.type] || 0;
                if (currentPrio > maxPrio) {
                    maxPrio = currentPrio;
                    activeEvent = e;
                }
            }
        }); 
        
        if (activeEvent) return activeEvent;
        
        return { type: 'Available', title: 'Verfügbar' }; 
    }    
    function renderCellCards() { 
        dom.cellCardsContainer.innerHTML = ''; 
        if (allTestCells.length === 0) { 
            dom.cellCardsContainer.innerHTML = '<p class="col-span-full text-center text-slate-500">Keine Testzellen konfiguriert.</p>'; 
            return; 
        } 
        
        allTestCells.forEach(cell => { 
            const statusEvent = getCurrentStatus(cell); 
            const status = statusEvent.type;
            
            const statusClasses = { 
                Available: { bg: 'bg-green-50', text: 'text-green-800', border: 'border-green-200', name: 'text-green-900', badge: 'bg-green-200' }, 
                Operational: { bg: 'bg-slate-50', text: 'text-slate-800', border: 'border-slate-200', name: 'text-slate-900', badge: 'bg-slate-200' }, 
                Maintenance: { bg: 'bg-yellow-50', text: 'text-yellow-800', border: 'border-yellow-200', name: 'text-yellow-900', badge: 'bg-yellow-200' }, 
                Blocked: { bg: 'bg-red-50', text: 'text-red-800', border: 'border-red-200', name: 'text-red-900', badge: 'bg-red-200' }, 
            }; 
            
            const c = statusClasses[status] || statusClasses.Operational; 
            
            // Titel-Logik: Bei Blocked/Maintenance den Event-Titel anzeigen, sonst Standard
            let statusLabel = status;
            let detailText = `ID: ${cell.ID}`;
            
            if (status === 'Blocked') {
                statusLabel = "GESPERRT";
                // Zeige den Grund an (z.B. "⛔ Sperre: Lüftung")
                detailText = `<span class="font-bold text-red-700">${statusEvent.title}</span>`;
            } else if (status === 'Maintenance') {
                detailText = `<span class="font-medium">${statusEvent.title}</span>`;
            } else if (status === 'Operational') {
                detailText = `Belegt: ${statusEvent.title}`;
            }
    
            const card = document.createElement('div'); 
            card.className = `cell-card cursor-pointer rounded-xl p-5 shadow-md ${c.bg} ${c.text} ${c.border} hover:border-gray-400 ${status === 'Operational' ? 'operational-pulse' : ''}`; 
            card.innerHTML = `
                <div class="flex items-center justify-between mb-3">
                    <h3 class="text-lg font-semibold ${c.name}">${cell.TESTCELL}</h3>
                    <span class="text-xs font-semibold px-3 py-1 rounded-full ${c.badge}">${statusLabel}</span>
                </div>
                <div class="text-sm font-medium min-h-[1.25rem]">${detailText}</div>
                <div class="mt-4 pt-4 border-t ${c.border}">
                    <div class="flex items-center text-sm">
                        <span class="material-icons-outlined mr-1.5" style="font-size: 18px;">engineering</span>
                        <span class="font-medium truncate">${cell.TYPES.join(', ') || 'Keine Angabe'}</span>
                    </div>
                </div>`; 
            
            card.addEventListener('click', () => openTestCellModal(cell)); 
            dom.cellCardsContainer.appendChild(card); 
        }); 
    }    
    function renderTimelineView() { const totalDays = 14; let dateHeaderHtml = ''; let shiftHeaderHtml = ''; const dayNames = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"]; const monthNames = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"]; let currentDate = new Date(timelineStartDate); for (let i = 0; i < totalDays; i++) { const day = new Date(currentDate); const isWeekend = day.getUTCDay() === 0 || day.getUTCDay() === 6; const isHoliday = !isWeekend && freeDaysSet.has(day.toISOString().slice(0,10)); const weekendClass = isWeekend ? 'weekend-bg' : (isHoliday ? 'holiday-bg' : ''); dateHeaderHtml += `<th colspan="2" class="px-1 py-1 text-center text-xs font-medium text-slate-600 w-[90px] whitespace-nowrap border-r border-slate-200 last:border-r-0 align-middle ${weekendClass}">${dayNames[day.getUTCDay()]}, ${day.getUTCDate()}. ${monthNames[day.getUTCMonth()]}</th>`; for(const shift of ['AM', 'PM']) { const isCurrent = toLocalDateString(new Date()) === toLocalDateString(day) && (new Date().getHours() < 12 ? 'AM' : 'PM') === shift; const currentClass = isCurrent ? 'current-timeslot-highlight' : ''; shiftHeaderHtml += `<th class="px-1 py-1 text-center text-xs font-medium text-slate-500 w-[45px] whitespace-nowrap border-r border-slate-200 last:border-r-0 align-middle ${weekendClass} ${currentClass}">${shift}</th>`; } currentDate.setUTCDate(currentDate.getUTCDate() + 1); } let fixedBodyHtml = ''; let scrollableBodyHtml = ''; allTestCells.forEach(cell => { fixedBodyHtml += `<tr class="h-[65px]"><td class="whitespace-nowrap px-4 py-3 text-sm text-slate-700 align-middle cursor-pointer hover:bg-slate-50" data-cell-id-fixed="${cell.ID}">${cell.TESTCELL}</td></tr>`; const totalShifts = totalDays * 2; let occupiedShifts = new Array(totalShifts).fill(null); const timelineStartUTC = new Date(timelineStartDate); cell.events.forEach(event => { const start = parseUTCDate(event.start_date); const end = parseUTCDate(event.end_date); const dayInMillis = 86400000; const startDayIndex = Math.floor((start.getTime() - timelineStartUTC.getTime()) / dayInMillis); const endDayIndex = Math.floor((end.getTime() - timelineStartUTC.getTime()) / dayInMillis); const startShiftIndex = startDayIndex * 2 + (event.start_shift === 'PM' ? 1 : 0); let endShiftIndex = endDayIndex * 2 + (event.end_shift === 'PM' ? 1 : 0); if (event.type !== 'Operational') { endShiftIndex = endShiftIndex + 1; } let firstSet = false; for (let i = startShiftIndex; i < endShiftIndex; i++) { if (i >= 0 && i < totalShifts && occupiedShifts[i] === null) { occupiedShifts[i] = { event: event, isStart: !firstSet }; firstSet = true; } } }); let rowHtml = '<tr class="h-[65px]">'; let i = 0; while (i < totalShifts) { const shiftInfo = occupiedShifts[i]; const dayIndex = Math.floor(i / 2); const shiftDate = new Date(timelineStartUTC); shiftDate.setUTCDate(shiftDate.getUTCDate() + dayIndex); const shiftType = i % 2 === 0 ? 'AM' : 'PM'; const isWeekend = shiftDate.getUTCDay() === 0 || shiftDate.getUTCDay() === 6; const isHoliday = !isWeekend && freeDaysSet.has(shiftDate.toISOString().slice(0,10)); const weekendClass = isWeekend ? 'weekend-bg' : (isHoliday ? 'holiday-bg' : ''); const isCurrent = toLocalDateString(new Date()) === toLocalDateString(shiftDate) && (new Date().getHours() < 12 ? 'AM' : 'PM') === shiftType; const currentClass = isCurrent ? 'current-timeslot-highlight' : ''; if (shiftInfo && shiftInfo.isStart) { let colspan = 1; while (i + colspan < totalShifts && occupiedShifts[i + colspan] && occupiedShifts[i + colspan].event.event_id === shiftInfo.event.event_id) { colspan++; } const event = shiftInfo.event; const colorClasses = { Operational: 'bg-slate-500 hover:bg-slate-600', Maintenance: 'bg-yellow-500 hover:bg-yellow-600', Blocked: 'bg-red-500 hover:bg-red-700', }; const isReadOnly = event.type === 'Operational'; const cursorClass = isReadOnly ? 'cursor-default' : 'cursor-pointer'; let title = '';
if (event.type === 'Blocked') {
    // Hier steht jetzt unser injizierter Titel (z.B. "⛔ Sperre: Anlage X")
    title = `${event.title}\n${event.description || ''}`;
} else if (isReadOnly) {
    title = `Operativer Block: ${event.title}\n${event.description || ''}`;
} else {
    title = `Manuelle Blockade: ${event.title}`;
} rowHtml += `<td colspan="${colspan}" class="align-middle ${cursorClass}" title="${title}" data-cell-id="${cell.ID}" data-event-id="${event.event_id}" data-is-readonly="${isReadOnly}"><div class="h-full w-full rounded-md text-white text-xs flex items-center justify-center p-1 overflow-hidden ${colorClasses[event.type] || colorClasses.Operational}"><span class="truncate">${event.title}</span></div></td>`; i += colspan; } else if (shiftInfo) { i++; } else { rowHtml += `<td class="align-middle cursor-pointer ${weekendClass} ${currentClass}" data-cell-id="${cell.ID}" data-date="${toLocalDateString(shiftDate)}" data-shift="${shiftType}"></td>`; i++; } } rowHtml += '</tr>'; scrollableBodyHtml += rowHtml; }); dom.timelineContainer.innerHTML = `<div class="overflow-x-auto rounded-lg border border-slate-200"><div class="align-middle inline-block min-w-full"><div class="flex"><div class="sticky left-0 z-10 bg-white"><table class="min-w-[200px] divide-y divide-slate-200 border-r border-slate-200"><thead class="bg-slate-50"><tr><th rowspan="2" class="px-4 py-3.5 text-left text-sm font-semibold text-slate-900 align-middle">Testzelle</th></tr><tr></tr></thead><tbody class="divide-y divide-slate-200 bg-white">${fixedBodyHtml}</tbody></table></div><div class="flex-grow overflow-x-auto"><table class="min-w-full divide-y divide-slate-200 timeline-table"><thead class="bg-slate-50 sticky top-0 z-10"><tr>${dateHeaderHtml}</tr><tr>${shiftHeaderHtml}</tr></thead><tbody class="divide-y divide-slate-200 bg-white">${scrollableBodyHtml}</tbody></table></div></div></div></div>`; }
    function renderAnalyseView() { 
        if (!allTestCells || allTestCells.length === 0) { 
            dom.analyseChartsContainer.innerHTML = '<p class="col-span-full text-center text-slate-500">Keine Testzellen verfügbar.</p>'; 
            return; 
        } 
        
        dom.analyseViewOverviewBtn.classList.toggle('active', currentAnalyseView === 'overview'); 
        dom.analyseViewTrendBtn.classList.toggle('active', currentAnalyseView === 'trend'); 
        dom.analyseViewEngineBtn.classList.toggle('active', currentAnalyseView === 'engine'); 

        // Die Daten sind in allTestCells bereits komplett (inkl. Archiv)
        dom.analyseChartsContainer.innerHTML = ''; 
        const currentYear = new Date().getFullYear(); 
        
        if (currentAnalyseView === 'overview') { 
            dom.analyseChartsContainer.className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'; 
            allTestCells.forEach(cell => dom.analyseChartsContainer.appendChild(createOverviewChartCard(cell, calculateYearlyStats(cell, currentYear), currentYear))); 
        } else if (currentAnalyseView === 'trend') { 
            dom.analyseChartsContainer.className = 'grid grid-cols-1 lg:grid-cols-2 gap-6'; 
            allTestCells.forEach(cell => dom.analyseChartsContainer.appendChild(createTrendChartCard(cell, calculateMonthlyStats(cell, currentYear), currentYear))); 
        } else if (currentAnalyseView === 'engine') { 
            dom.analyseChartsContainer.className = 'grid grid-cols-1 lg:grid-cols-2 gap-8'; 
            allTestCells.forEach(cell => dom.analyseChartsContainer.appendChild(createEngineAnalysisCard(cell, calculateEngineStats(cell, currentYear), currentYear))); 
        }
    } 
    
    function getStatusForDateShift(cell, dateStr, shift) { const activeEvent = cell.events.find(e => { const startOk = (e.start_date < dateStr) || (e.start_date === dateStr && e.start_shift <= shift); let endOk = false; if (e.type === 'Operational') { endOk = (e.end_date > dateStr) || (e.end_date === dateStr && e.end_shift > shift); } else { endOk = (e.end_date > dateStr) || (e.end_date === dateStr && e.end_shift >= shift); } return startOk && endOk; }); return activeEvent ? activeEvent.type : 'Available'; }
    function calculateYearlyStats(cell, year) { let totalShifts = 0, availableShifts = 0, operationalShifts = 0, maintenanceShifts = 0, blockedShifts = 0; const startDate = new Date(year, 0, 1); const endDate = new Date(year, 11, 31); let currentDate = startDate; while (currentDate <= endDate) { if (currentDate.getDay() >= 1 && currentDate.getDay() <= 5) { const dateStr = toLocalDateString(currentDate); for (const shift of ['AM', 'PM']) { totalShifts++; const status = getStatusForDateShift(cell, dateStr, shift); if (status === 'Available') availableShifts++; else if (status === 'Operational') operationalShifts++; else if (status === 'Maintenance') maintenanceShifts++; else if (status === 'Blocked') blockedShifts++; } } currentDate.setDate(currentDate.getDate() + 1); } return { total: totalShifts, available: availableShifts, operational: operationalShifts, maintenance: maintenanceShifts, blocked: blockedShifts, availablePercent: totalShifts > 0 ? ((availableShifts / totalShifts) * 100).toFixed(1) : '0.0', operationalPercent: totalShifts > 0 ? ((operationalShifts / totalShifts) * 100).toFixed(1) : '0.0', maintenancePercent: totalShifts > 0 ? ((maintenanceShifts / totalShifts) * 100).toFixed(1) : '0.0', blockedPercent: totalShifts > 0 ? ((blockedShifts / totalShifts) * 100).toFixed(1) : '0.0' }; }
    function calculateMonthlyStats(cell, year) { const monthNames = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"]; const stats = { labels: monthNames, available: Array(12).fill(0), operational: Array(12).fill(0), maintenance: Array(12).fill(0), blocked: Array(12).fill(0) }; for (let month = 0; month < 12; month++) { const startDate = new Date(year, month, 1); const endDate = new Date(year, month + 1, 0); let currentDate = startDate; while (currentDate <= endDate) { if (currentDate.getDay() >= 1 && currentDate.getDay() <= 5) { const dateStr = toLocalDateString(currentDate); for (const shift of ['AM', 'PM']) { const status = getStatusForDateShift(cell, dateStr, shift); if (status === 'Available') stats.available[month]++; else if (status === 'Operational') stats.operational[month]++; else if (status === 'Maintenance') stats.maintenance[month]++; else if (status === 'Blocked') stats.blocked[month]++; } } currentDate.setDate(currentDate.getDate() + 1); } } return stats; }
    function deriveEngineTypeFromDescription(description, compatibleTypes) { if (!description || !compatibleTypes || compatibleTypes.length === 0) return "Nicht zugeordnet"; const descLower = description.toLowerCase(); for (const type of compatibleTypes) { if (descLower.includes(type.toLowerCase())) return type; } return "Nicht zugeordnet"; }
    function calculateShiftDuration(event) { let shiftCount = 0; let current = parseUTCDate(event.start_date); const end = parseUTCDate(event.end_date); while (current <= end) { if (current.getUTCDay() >= 1 && current.getUTCDay() <= 5) { const isStartDay = current.getTime() === parseUTCDate(event.start_date).getTime(); const isEndDay = current.getTime() === end.getTime(); if (isStartDay && isEndDay) shiftCount += (event.start_shift === 'AM' && event.end_shift === 'PM') ? 2 : 1; else if (isStartDay) shiftCount += (event.start_shift === 'AM' ? 2 : 1); else if (isEndDay) shiftCount += (event.end_shift === 'PM' ? 2 : 1); else shiftCount += 2; } current.setUTCDate(current.getUTCDate() + 1); } return shiftCount; }
    function calculateEngineStats(cell, year) { const stats = {}; const operationalEvents = cell.events.filter(e => e.type === 'Operational' && new Date(e.start_date).getUTCFullYear() === year); operationalEvents.forEach(event => { const engineType = deriveEngineTypeFromDescription(event.description, cell.TYPES); const workorder = event.title || 'Unbekannt'; const shifts = calculateShiftDuration(event); if (!stats[engineType]) stats[engineType] = { totalTests: 0, totalShifts: 0, avgShifts: 0, workorders: [] }; stats[engineType].totalTests++; stats[engineType].totalShifts += shifts; stats[engineType].workorders.push({ workorder: workorder, shifts: shifts }); }); for(const type in stats) { if(stats[type].totalTests > 0) stats[type].avgShifts = stats[type].totalShifts / stats[type].totalTests; } return stats; }
    function createOverviewChartCard(cell, stats, year) { const card = document.createElement('div'); card.className = 'bg-white rounded-lg shadow-md p-6 border border-slate-200'; const canvasId = `chart-overview-${cell.ID.replace(/[^a-zA-Z0-9]/g, '-')}`; card.innerHTML = `<div class="mb-4"><h3 class="text-lg font-semibold text-slate-900">${cell.TESTCELL}</h3><p class="text-sm text-slate-600">ID: ${cell.ID}</p></div><div class="relative h-64"><canvas id="${canvasId}"></canvas></div><div class="mt-4 pt-4 border-t border-slate-200"><div class="grid grid-cols-2 gap-3 text-xs"><div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full bg-green-500"></div><span>Verfügbar: <strong>${stats.availablePercent}%</strong></span></div><div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full bg-slate-500"></div><span>Operational: <strong>${stats.operationalPercent}%</strong></span></div><div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full bg-yellow-500"></div><span>Wartung: <strong>${stats.maintenancePercent}%</strong></span></div><div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full bg-red-500"></div><span>Blockiert: <strong>${stats.blockedPercent}%</strong></span></div></div><div class="mt-3 text-xs text-slate-600 text-center">Gesamt: ${stats.total} Schichten in ${year}</div></div>`; setTimeout(() => { const ctx = document.getElementById(canvasId); if (ctx) { new Chart(ctx, { type: 'doughnut', data: { labels: ['Verfügbar', 'Operational', 'Wartung', 'Blockiert'], datasets: [{ data: [stats.available, stats.operational, stats.maintenance, stats.blocked], backgroundColor: ['rgb(34, 197, 94)','rgb(100, 116, 139)','rgb(234, 179, 8)','rgb(239, 68, 68)'], borderWidth: 2, borderColor: '#ffffff' }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = total > 0 ? ((context.parsed / total) * 100).toFixed(1) : 0; return `${context.label}: ${context.parsed} Schichten (${percentage}%)`; } } } } } }); } }, 50); return card; }
    function createTrendChartCard(cell, stats, year) { const card = document.createElement('div'); card.className = 'bg-white rounded-lg shadow-md p-6 border border-slate-200'; const canvasId = `chart-trend-${cell.ID.replace(/[^a-zA-Z0-9]/g, '-')}`; card.innerHTML = `<div class="mb-4"><h3 class="text-lg font-semibold text-slate-900">${cell.TESTCELL}</h3><p class="text-sm text-slate-600">Monatliche Auslastung in ${year}</p></div><div class="relative h-80"><canvas id="${canvasId}"></canvas></div>`; setTimeout(() => { const ctx = document.getElementById(canvasId); if (ctx) { new Chart(ctx, { type: 'line', data: { labels: stats.labels, datasets: [ { label: 'Operational', data: stats.operational, backgroundColor: 'rgba(100, 116, 139, 0.5)', borderColor: 'rgb(100, 116, 139)', fill: true, tension: 0.4 }, { label: 'Wartung', data: stats.maintenance, backgroundColor: 'rgba(234, 179, 8, 0.5)', borderColor: 'rgb(234, 179, 8)', fill: true, tension: 0.4 }, { label: 'Blockiert', data: stats.blocked, backgroundColor: 'rgba(239, 68, 68, 0.5)', borderColor: 'rgb(239, 68, 68)', fill: true, tension: 0.4 }, { label: 'Verfügbar', data: stats.available, backgroundColor: 'rgba(34, 197, 94, 0.5)', borderColor: 'rgb(34, 197, 94)', fill: true, tension: 0.4 }, ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: { y: { stacked: true, beginAtZero: true, title: { display: true, text: 'Anzahl Schichten' } }, x: { title: { display: true, text: 'Monat' } } }, plugins: { legend: { position: 'bottom' }, tooltip: { callbacks: { label: function(context) { return `${context.dataset.label}: ${context.parsed.y} Schichten`; } } } } } }); } }, 50); return card; }
    function createEngineAnalysisCard(cell, engineStats, year) { const card = document.createElement('div'); card.className = 'bg-white rounded-lg shadow-md p-6 border border-slate-200'; const canvasId = `chart-engine-${cell.ID.replace(/[^a-zA-Z0-9]/g, '-')}`; let contentHtml = `<div class="mb-4"><h3 class="text-lg font-semibold text-slate-900">${cell.TESTCELL}</h3><p class="text-sm text-slate-600">Analyse der Triebwerkstests in ${year}</p></div>`; const sortedEngineTypes = Object.keys(engineStats); if (sortedEngineTypes.length === 0) { contentHtml += '<div class="text-center text-slate-500 py-8">Keine operativen Tests in diesem Jahr erfasst.</div>'; } else { contentHtml += `<div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr class="text-left text-slate-600"><th class="px-4 py-2 font-semibold">Workorder</th><th class="px-4 py-2 font-semibold text-center">Belegte Schichten</th></tr></thead><tbody class="divide-y divide-slate-200">`; sortedEngineTypes.sort((a, b) => engineStats[b].totalShifts - engineStats[a].totalShifts).forEach(type => { const stats = engineStats[type]; contentHtml += `<tr class="bg-slate-100 font-semibold text-slate-800"><td colspan="2" class="px-4 py-2.5">Triebwerkstyp: ${type} <span class="font-normal text-slate-600 ml-2">(${stats.totalTests} Tests, ${stats.totalShifts} Schichten, Ø ${stats.avgShifts.toFixed(1)})</span></td></tr>`; stats.workorders.sort((a,b) => b.shifts - a.shifts).forEach(wo => { contentHtml += `<tr class="hover:bg-slate-50"><td class="pl-8 pr-4 py-2 font-medium text-slate-900">${wo.workorder}</td><td class="px-4 py-2 text-center text-slate-700">${wo.shifts}</td></tr>`; }); }); contentHtml += `</tbody></table></div><div class="mt-6 pt-6 border-t border-slate-200"><h4 class="text-md font-semibold text-slate-800 mb-3">Visuelle Effizienz-Analyse</h4><div class="relative" style="height: 250px;"><canvas id="${canvasId}"></canvas></div></div>`; } card.innerHTML = contentHtml; if (sortedEngineTypes.length > 0) { setTimeout(() => { const ctx = document.getElementById(canvasId); const chartData = sortedEngineTypes.map(type => { const stats = engineStats[type]; return { label: type, x: stats.totalTests, y: stats.avgShifts, r: Math.max(stats.totalShifts, 5) }; }); if (ctx) { new Chart(ctx, { type: 'bubble', data: { datasets: [{ label: 'Triebwerkstypen', data: chartData, backgroundColor: (context) => { const c = context.raw.y; return c > 10 ? 'rgba(239, 68, 68, 0.6)' : c > 5 ? 'rgba(234, 179, 8, 0.6)' : 'rgba(37, 99, 235, 0.6)'; }, borderColor: (context) => { const c = context.raw.y; return c > 10 ? 'rgb(239, 68, 68)' : c > 5 ? 'rgb(234, 179, 8)' : 'rgb(37, 99, 235)'; }, }] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { beginAtZero: true, title: { display: true, text: 'Anzahl der Tests' } }, y: { beginAtZero: true, title: { display: true, text: 'Ø Schichten pro Test' } } }, plugins: { legend: { display: false }, tooltip: { callbacks: { label: (context) => [ `Typ: ${context.raw.label}`, `Anzahl Tests: ${context.raw.x}`, `Ø Schichten/Test: ${context.raw.y.toFixed(1)}`, `Gesamte Schichten: ${context.raw.r}` ] } } } } }); } }, 50); } return card; }
    function toggleModalEditState(enableEditing) { isModalInEditMode = enableEditing; dom.testCellNameInput.readOnly = !enableEditing; dom.engineTypesContainer.querySelectorAll('input').forEach(input => { input.readOnly = !enableEditing; input.classList.toggle('bg-slate-50', !enableEditing); }); dom.addEngineTypeBtn.style.display = enableEditing ? 'block' : 'none'; dom.engineTypesContainer.querySelectorAll('button').forEach(btn => { btn.style.display = enableEditing ? 'inline-flex' : 'none'; }); dom.saveTestCellBtn.style.display = enableEditing ? 'block' : 'none'; 
        dom.editTestCellBtn.style.display = enableEditing ? 'none' : (isEditMode && canManageCellMasterData ? 'block' : 'none');
        dom.deleteTestCellBtnHeader.style.display = (isEditMode && enableEditing && canManageCellMasterData) ? 'block' : 'none';
        dom.cancelTestCellModalBtn.textContent = enableEditing ? 'Abbrechen' : 'Schließen'; 
    }
    let availableFacilityUnits = [];

    // 1. Units laden (beim Start)
    async function loadModalFacilityUnits() {
        try {
            const res = await fetch('/api/facility/units');
            if (res.ok) {
                availableFacilityUnits = await res.json();
                const dl = document.getElementById('modalFacilityUnitList');
                dl.innerHTML = '';
                availableFacilityUnits.forEach(u => {
                    const opt = document.createElement('option');
                    opt.value = u.name; // "1.1 Name"
                    opt.dataset.id = u.id; // UUID
                    dl.appendChild(opt);
                });
            }
        } catch(e) { console.error(e); }
    }
    
    // 2. Unit Tag hinzufügen
    function addUnitTag(unitId, unitName, container) {
        // Prüfen ob schon da
        if (container.querySelector(`div[data-unit-id="${unitId}"]`)) return;
    
        const div = document.createElement('div');
        div.className = 'flex items-center justify-between p-2 bg-slate-50 border border-slate-200 rounded-md text-sm';
        div.dataset.unitId = unitId;
        
        div.innerHTML = `
            <span class="truncate font-medium text-slate-700">${unitName}</span>
            <button type="button" class="text-slate-400 hover:text-red-500 ml-2" onclick="this.parentElement.remove()">
                <span class="material-icons-outlined text-base">close</span>
            </button>
        `;
        container.appendChild(div);
    }
    function openTestCellModal(cell=null){ 
        isEditMode=!!cell; 
        dom.testCellModalTitle.textContent=isEditMode?`Testzelle "${cell.TESTCELL}"`:'Neue Testzelle anlegen'; 
        dom.saveTestCellBtn.textContent=isEditMode?'Änderungen speichern':'Testzelle erstellen'; 
        
        dom.editTestCellBtn.style.display = isEditMode && canManageCellMasterData ? 'block' : 'none';
        dom.deleteTestCellBtnHeader.style.display = 'none';

        if(isEditMode){ 
            currentEditingCell=cell; 
            dom.originalTestCellIdInput.value=cell.ID; 
            dom.testCellIdInput.value=cell.ID; 
            dom.testCellIdInput.readOnly=true; 
            dom.testCellIdInput.classList.add('bg-slate-50'); 
            dom.testCellNameInput.value=cell.TESTCELL; 
            renderEngineTypes(cell.TYPES); 
            toggleModalEditState(false); 
        } else { 
            currentEditingCell=null; 
            dom.originalTestCellIdInput.value=''; 
            dom.testCellIdInput.value=''; 
            dom.testCellIdInput.readOnly=false; 
            dom.testCellIdInput.classList.remove('bg-slate-50'); 
            dom.testCellNameInput.value=''; 
            renderEngineTypes(['']); 
            toggleModalEditState(true); 
            dom.editTestCellBtn.style.display = 'none';
            dom.deleteTestCellBtnHeader.style.display = 'none'; 
        } 
        dom.testCellModal.classList.add('is-open'); 
            const unitContainer = document.getElementById('requiredUnitsContainer');
        
        unitContainer.innerHTML = '';
        document.getElementById('addRequiredUnitInput').value = '';
    
        if (isEditMode && cell.REQUIRED_UNITS) {
            // REQUIRED_UNITS ist eine Liste von IDs
            cell.REQUIRED_UNITS.forEach(uid => {
                // Namen auflösen
                const unitObj = availableFacilityUnits.find(u => u.id === uid);
                const name = unitObj ? unitObj.name : "Unbekannte ID";
                addUnitTag(uid, name, unitContainer);
            });
        }
    }
    function renderEngineTypes(types){ dom.engineTypesContainer.innerHTML=''; const typesToShow=types.length>0?types:['']; typesToShow.forEach((type,index)=>{ addEngineTypeInput(type,typesToShow.length>1); }); }
    function addEngineTypeInput(value='', showRemoveBtn=false) { 
        const div = document.createElement('div'); 
        div.className = 'flex items-center gap-2'; 
        const input = document.createElement('input'); 
        input.type = 'text'; 
        input.className = 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm flex-grow'; 
        input.placeholder = 'z.B. CFM56'; 
        input.value = value; 
        input.setAttribute('list', 'availableEngineTypesList'); 
        input.readOnly = !isModalInEditMode; 
        if (!isModalInEditMode) { input.classList.add('bg-slate-50'); } 
        div.appendChild(input); 
        
        if(showRemoveBtn) { 
            const removeBtn = document.createElement('button'); 
            removeBtn.type = 'button'; 
            removeBtn.className = 'text-slate-400 hover:text-slate-600 p-1 items-center justify-center'; 
            removeBtn.style.display = isModalInEditMode ? 'inline-flex' : 'none'; 
            removeBtn.innerHTML = '<span class="material-icons-outlined text-xl">delete_outline</span>'; 
            removeBtn.onclick = () => div.remove(); 
            div.appendChild(removeBtn); 
        } 
        dom.engineTypesContainer.appendChild(div); 
    }    
    
    function closeTestCellModal(){ dom.testCellModal.classList.remove('is-open'); }
    function handleSaveTestCell(){ const id=dom.testCellIdInput.value.trim(); const name=dom.testCellNameInput.value.trim(); const requiredUnitIds = Array.from(document.getElementById('requiredUnitsContainer').children).map(div => div.dataset.unitId); if(!id||!name){ alert('ID und Name sind Pflichtfelder.'); return; } const engineTypes=Array.from(dom.engineTypesContainer.querySelectorAll('input')).map(input=>input.value.trim()).filter(Boolean); if(isEditMode){ const cell=allTestCells.find(c=>c.ID===dom.originalTestCellIdInput.value); if(cell){ cell.ID=id; cell.TESTCELL=name; cell.TYPES=engineTypes; cell.REQUIRED_UNITS = requiredUnitIds;} }else{ if(allTestCells.some(c=>c.ID===id)){ alert('Eine Testzelle mit dieser ID existiert bereits.'); return; } allTestCells.push({ID:id,TESTCELL:name,TYPES:engineTypes,REQUIRED_UNITS: requiredUnitIds,events:[]}); } toggleModalEditState(false); saveData(); renderAll(); closeTestCellModal(); }
    function handleDeleteTestCell(){ if(!isEditMode||!confirm(`Möchten Sie die Testzelle "${currentEditingCell.TESTCELL}" wirklich löschen?`))return; allTestCells=allTestCells.filter(c=>c.ID!==currentEditingCell.ID); saveData(); renderAll(); closeTestCellModal(); }
    function toggleEventModalEditState(enableEditing) { 
        isModalInEditMode = enableEditing; 
        const inputs = [dom.eventTitleInput, dom.eventStartDateInput, dom.eventEndDateInput, dom.eventDescriptionInput]; 
        const selects = [dom.eventStartShiftSelect, dom.eventEndShiftSelect]; 
        inputs.forEach(input => { input.readOnly = !enableEditing; input.classList.toggle('bg-slate-50', !enableEditing); }); 
        selects.forEach(select => { select.disabled = !enableEditing; select.classList.toggle('bg-slate-50', !enableEditing); }); 
        dom.eventTypeButtons.querySelectorAll('button').forEach(btn => { btn.disabled = !enableEditing; btn.classList.toggle('opacity-75', !enableEditing); btn.classList.toggle('pointer-events-none', !enableEditing); }); 
        dom.saveEventBtn.style.display = enableEditing ? 'block' : 'none'; 
        dom.editEventBtn.style.display = enableEditing ? 'none' : (isEditMode && canManageCellEvents ? 'block' : 'none');
        dom.deleteEventBtn.style.display = (isEditMode && enableEditing && canManageCellEvents) ? 'block' : 'none';
        dom.cancelEventModalBtn.textContent = enableEditing ? 'Abbrechen' : 'Schließen'; 
    }
    function openEventModal(cellId,event=null,date=null,shift=null){ 
        isEditMode=!!event; 
        currentEditingEvent=event; 
        const cell=allTestCells.find(c=>c.ID===cellId); 
        if(!cell)return; 
        dom.eventModalTitle.textContent=isEditMode?`Blockade/Wartung ansehen`:`Neue Blockade/Wartung für ${cell.TESTCELL}`; 
        dom.saveEventBtn.textContent=isEditMode?'Änderungen speichern':'Blockade erstellen'; 
        
        dom.editEventBtn.style.display = isEditMode && canManageCellEvents ? 'block' : 'none';
        dom.deleteEventBtn.style.display = 'none';

        if(isEditMode){ 
            dom.eventModalEventId.value=event.event_id; 
            dom.eventModalCellId.value=event.cell_id; 
            dom.eventTitleInput.value=event.title; 
            dom.eventStartDateInput.value=event.start_date; 
            dom.eventStartShiftSelect.value=event.start_shift; 
            dom.eventEndDateInput.value=event.end_date; 
            dom.eventEndShiftSelect.value=event.end_shift; 
            dom.eventDescriptionInput.value=event.description||''; 
            updateEventTypeButtons(event.type); 
            dom.trackingInfoContainer.style.display = 'block';
            dom.eventCreatedBy.textContent = event.created_by || 'System';
            dom.eventCreatedAt.textContent = event.created_at ? new Date(event.created_at).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short'}) : 'N/A';
            dom.eventModifiedBy.textContent = event.last_modified_by || 'N/A';
            dom.eventModifiedAt.textContent = event.last_modified_at ? new Date(event.last_modified_at).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short'}) : 'N/A';
            toggleEventModalEditState(false); 
        } else { 
            dom.eventModalEventId.value=''; 
            dom.eventModalCellId.value=cellId; 
            dom.eventTitleInput.value=''; 
            dom.eventStartDateInput.value=date; 
            dom.eventStartShiftSelect.value=shift; 
            dom.eventEndDateInput.value=date; 
            dom.eventEndShiftSelect.value=shift; 
            dom.eventDescriptionInput.value=''; 
            updateEventTypeButtons('Maintenance'); 
            dom.trackingInfoContainer.style.display = 'none'; 
            toggleEventModalEditState(true); 
            dom.editEventBtn.style.display = 'none'; 
            dom.deleteEventBtn.style.display = 'none'; 
        } 
        dom.eventModal.classList.add('is-open'); 
    }
    function updateEventTypeButtons(activeType){ dom.eventTypeButtons.querySelectorAll('button').forEach(btn=>{ btn.classList.toggle('bg-blue-600', btn.dataset.type===activeType); btn.classList.toggle('text-white', btn.dataset.type===activeType); }); }
    function closeEventModal(){ dom.eventModal.classList.remove('is-open'); }
    function handleSaveEvent(){ const title=dom.eventTitleInput.value.trim(); if(!title){ alert('Ein Titel ist erforderlich.'); return; } const cellId=dom.eventModalCellId.value; const cell=allTestCells.find(c=>c.ID===cellId); if(!cell)return; const nowIso = new Date().toISOString(); let eventData; if (isEditMode) { eventData = { ...currentEditingEvent, event_id: currentEditingEvent.event_id, cell_id: cellId, title: title, type: dom.eventTypeButtons.querySelector('.bg-blue-600').dataset.type, start_date: dom.eventStartDateInput.value, start_shift: dom.eventStartShiftSelect.value, end_date: dom.eventEndDateInput.value, end_shift: dom.eventEndShiftSelect.value, description: dom.eventDescriptionInput.value.trim(), last_modified_by: CURRENT_USER_DISPLAY_NAME, last_modified_at: nowIso }; } else { eventData = { event_id: `manual_${Date.now()}`, cell_id: cellId, title: title, type: dom.eventTypeButtons.querySelector('.bg-blue-600').dataset.type, start_date: dom.eventStartDateInput.value, start_shift: dom.eventStartShiftSelect.value, end_date: dom.eventEndDateInput.value, end_shift: dom.eventEndShiftSelect.value, description: dom.eventDescriptionInput.value.trim(), created_by: CURRENT_USER_DISPLAY_NAME, created_at: nowIso, last_modified_by: '', last_modified_at: '' }; } if(new Date(eventData.end_date)<new Date(eventData.start_date)){ alert('Das Enddatum darf nicht vor dem Startdatum liegen.'); return; } if(isEditMode){ const index=cell.events.findIndex(e=>e.event_id===eventData.event_id); if(index>-1)cell.events[index]=eventData; }else{ cell.events.push(eventData); } saveData(); renderAll(); closeEventModal(); }
    function handleDeleteEvent(){ if(!isEditMode||!confirm(`Möchten Sie diesen Eintrag wirklich löschen?`))return; const cell=allTestCells.find(c=>c.ID===currentEditingEvent.cell_id); if(cell) cell.events=cell.events.filter(e=>e.event_id!==currentEditingEvent.event_id); saveData(); renderAll(); closeEventModal(); }

    dom.viewTabs.addEventListener('click', (e) => { const btn = e.target.closest('.tab-btn'); if (btn && btn.dataset.view) switchView(btn.dataset.view); });
    dom.prevWeekBtn.addEventListener('click', () => { timelineStartDate.setUTCDate(timelineStartDate.getUTCDate() - 7); renderTimelineView(); });
    dom.nextWeekBtn.addEventListener('click', () => { timelineStartDate.setUTCDate(timelineStartDate.getUTCDate() + 7); renderTimelineView(); });
    dom.jumpToTodayBtn.addEventListener('click', () => { timelineStartDate = getStartOfWeek(new Date()); renderTimelineView(); });
    dom.editTestCellBtn.addEventListener('click', () => toggleModalEditState(true)); 
    if(dom.newTestCellBtn) dom.newTestCellBtn.addEventListener('click', () => openTestCellModal()); 
    dom.closeTestCellModalBtn.addEventListener('click', closeTestCellModal);
    dom.cancelTestCellModalBtn.addEventListener('click', () => { if (isModalInEditMode) { if (isEditMode) openTestCellModal(currentEditingCell); else closeTestCellModal(); } else closeTestCellModal(); });
    dom.addEngineTypeBtn.addEventListener('click', () => addEngineTypeInput('', true)); 
    dom.saveTestCellBtn.addEventListener('click', handleSaveTestCell); 
    dom.deleteTestCellBtnHeader.addEventListener('click', handleDeleteTestCell);
    dom.editEventBtn.addEventListener('click', () => { dom.eventModalTitle.textContent = 'Blockade/Wartung bearbeiten'; toggleEventModalEditState(true); });
    dom.closeEventModalBtn.addEventListener('click', closeEventModal);
    dom.cancelEventModalBtn.addEventListener('click', () => { if (isModalInEditMode) { if (isEditMode) openEventModal(currentEditingEvent.cell_id, currentEditingEvent); else closeEventModal(); } else closeEventModal(); });
    dom.saveEventBtn.addEventListener('click', handleSaveEvent); 
    dom.deleteEventBtn.addEventListener('click', handleDeleteEvent);
    dom.eventTypeButtons.addEventListener('click', e => { const btn = e.target.closest('button'); if (btn) updateEventTypeButtons(btn.dataset.type); });
    dom.analyseViewOverviewBtn.addEventListener('click', () => { if (currentAnalyseView !== 'overview') { currentAnalyseView = 'overview'; renderAnalyseView(); } });
    dom.analyseViewTrendBtn.addEventListener('click', () => { if (currentAnalyseView !== 'trend') { currentAnalyseView = 'trend'; renderAnalyseView(); } });
    dom.analyseViewEngineBtn.addEventListener('click', () => { if (currentAnalyseView !== 'engine') { currentAnalyseView = 'engine'; renderAnalyseView(); } });
    document.body.addEventListener('click', (e) => { const fixedCell = e.target.closest('[data-cell-id-fixed]'); if (fixedCell) { const cell = allTestCells.find(c => c.ID === fixedCell.dataset.cellIdFixed); if (cell) openTestCellModal(cell); } });
    dom.timelineContainer.addEventListener('click', e => { 
        const cellTd = e.target.closest('td'); 
        if (!cellTd) return; 

        if (!canManageCellEvents) return;
        
        const cellId = cellTd.dataset.cellId; 
        const eventId = cellTd.dataset.eventId; 
        
        // --- NEUE LOGIK FÜR VIRTUELLE EVENTS ---
        // Wir erkennen virtuelle Events am Präfix oder am readOnly Flag + Typ Blocked
        const isReadOnly = cellTd.dataset.isReadonly === 'true'; 
        
        if (eventId && eventId.startsWith('virt_block_')) {
             // Extrahiere Issue ID aus eventId (Format: virt_block_ISSUEID_CELLID)
             // Etwas hacky, besser wir holen das echte Event Objekt
             const cell = allTestCells.find(c => c.ID === cellId);
             const event = cell ? cell.events.find(ev => ev.event_id === eventId) : null;
             
             if (event && event.sourceIssueId) {
                 if(confirm(`Dieser Block wird durch Issue ${event.sourceIssueId} gesteuert.\nMöchten Sie zum Maintenance Center wechseln, um das Issue zu bearbeiten?`)) {
                     // Weiterleitung zum Maintenance Center mit Deep Link
                     window.location.href = `/maintenance?openId=${event.sourceIssueId}`;
                 }
                 return;
             }
        }
        // ---------------------------------------

        if (isReadOnly) { 
            alert(`Dieser Block wird durch die Projekt-Timeline gesteuert und kann hier nicht bearbeitet werden.\n${cellTd.title}`); 
            return; 
        } 
        
        const cell = allTestCells.find(c => c.ID === cellId); 
        if (!cell) return; 
        if (eventId) { 
            const event = cell.events.find(e => e.event_id == eventId); 
            if (event) openEventModal(cellId, event); 
        } else { 
            const date = cellTd.dataset.date; 
            const shift = cellTd.dataset.shift; 
            if (date && shift) openEventModal(cellId, null, date, shift); 
        } 
    });
    
    async function loadEngineTypeSuggestions() {
        try {
            const response = await fetch('/api/process_template_options');
            if (response.ok) {
                const data = await response.json();
                // Wir interessieren uns nur für die Schlüssel (Engine Types), nicht für die Events
                const engineTypes = Object.keys(data).sort();
                
                // Datalist Element erstellen oder leeren
                let datalist = document.getElementById('availableEngineTypesList');
                if (!datalist) {
                    datalist = document.createElement('datalist');
                    datalist.id = 'availableEngineTypesList';
                    document.body.appendChild(datalist);
                }
                datalist.innerHTML = ''; // Reset

                engineTypes.forEach(type => {
                    const option = document.createElement('option');
                    option.value = type;
                    datalist.appendChild(option);
                });
            }
        } catch (error) {
            console.warn("Konnte Engine-Type-Vorschläge nicht laden:", error);
        }
    }
    

    loadEngineTypeSuggestions();
    loadData();
    loadModalFacilityUnits();
    
    // Event Listener für Add Button
    document.getElementById('btnAddRequiredUnit').addEventListener('click', () => {
        const input = document.getElementById('addRequiredUnitInput');
        const val = input.value.trim();
        if(!val) return;
        
        // ID finden
        const unitObj = availableFacilityUnits.find(u => u.name === val);
        if(unitObj) {
            addUnitTag(unitObj.id, unitObj.name, document.getElementById('requiredUnitsContainer'));
            input.value = '';
        } else {
            alert("Bitte eine gültige Anlage aus der Liste wählen.");
        }
    });
});
</script>
</body>
</html>
"""

QUALIFICATIONS_TABLE_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Inter%3Awght%40400%3B500%3B700%3B900&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet"/>
    <title>MBET - Qualifikationsübersicht</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
    <style>
        body { font-family: 'Inter', "Noto Sans", sans-serif; }
        .material-icons-outlined, .material-symbols-outlined { font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; }

        /* Tabelle — angelehnt an Anwesenheitsplanung */
        .qual-table { border-collapse: collapse; width: 100%; font-size: 0.75rem; }
        .qual-table th, .qual-table td { border: 1px solid #e2e8f0; vertical-align: middle; }
        .qual-table thead th { position: sticky; top: 0; background: #f8fafc; z-index: 15; padding: 0.5rem 0.75rem; font-weight: 600; color: #475569; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.7rem; text-align: center; white-space: nowrap; }
        .qual-table thead th.col-name { text-align: left; }
        .qual-table tbody td { padding: 0.4rem 0.6rem; color: #1e293b; }
        .qual-table tbody tr:hover td { background: #f8fafc !important; }
        /* Sticky Spalten */
        .sticky-col-name { position: sticky; left: 0; background: #fff; z-index: 5; min-width: 13rem; width: 13rem; }
        .sticky-col-id   { position: sticky; left: 13rem; background: #fff; z-index: 5; min-width: 5rem; width: 5rem; border-right: 2px solid #e2e8f0 !important; }
        .qual-table thead th.sticky-col-name { background: #f8fafc; z-index: 20; }
        .qual-table thead th.sticky-col-id   { background: #f8fafc; z-index: 20; }
        .qual-table tbody tr:hover .sticky-col-name,
        .qual-table tbody tr:hover .sticky-col-id { background: #f8fafc !important; }
        /* Status-Dots */
        .status-dot        { display: inline-block; width: 11px; height: 11px; border-radius: 50%; vertical-align: middle; }
        .status-dot-placeholder { display: inline-block; width: 11px; height: 11px; border-radius: 50%; border: 1px dashed #cbd5e1; vertical-align: middle; }
        .status-X  { background: #10b981; }
        .status-TS { background: #f59e0b; }
        .status-TT { background: #ef4444; }
        .status-TP { background: #6b7280; }
        .cell-content-wrapper { display: flex; align-items: center; justify-content: center; gap: 3px; }
        .cert-icon { font-size: 14px; color: #94a3b8; }
        .editable-qual-cell { cursor: pointer; text-align: center; }
        /* Gruppen-Trennzeile */
        .qual-group-row td { font-weight: 600; font-size: 0.7rem; padding: 0.3rem 0.75rem; border-bottom: 2px solid; }
        /* Modal */
        .hidden { display: none !important; }
        .form-input-modal, .form-select-modal { display: block; width: 100%; padding: 0.5rem 0.75rem; background: #fff; border: 1px solid #cbd5e1; border-radius: 0.5rem; font-size: 0.875rem; outline: none; }
        .form-input-modal:focus, .form-select-modal:focus { ring: 2px solid #3b82f6; border-color: #3b82f6; }
        .form-input-modal.is-invalid { border-color: #ef4444; color: #dc2626; }
        #merkmaleListe li { user-select: none; }
        #merkmaleListe li.dragging { opacity: 0.5; background-color: #cceeff; }
        .text-primary { color: #3B82F6; }
        .custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
        .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
        .custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
        .qa-select { display: block; width: 100%; padding: 0.375rem 0.75rem; background: #fff; border: 1px solid #d1d5db; border-radius: 0.375rem; font-size: 0.875rem; color: #374151; }
        .apexcharts-tooltip-custom { background: #fff; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 12px; color: #334155; }

        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
            <h1 class="text-2xl font-bold tracking-tight text-slate-800">Qualifikationsübersicht</h1>
        </header>

        <!-- Tab-Nav — frei auf neutral bg, wie test_cell_management -->
        <div class="px-4 sm:px-6 lg:px-8 pt-4 lg:pt-8">
            <div class="border-b border-slate-200">
                <nav class="-mb-px flex space-x-8">
                    <button id="tab-btn-overview" onclick="switchQualiTab('overview')" class="qual-tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-primary text-primary">Übersicht</button>
                    <button id="tab-btn-analyse" onclick="switchQualiTab('analyse')" class="qual-tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Analyse</button>
                    <button id="tab-btn-matrix" onclick="switchQualiTab('matrix')" class="qual-tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Kompetenz-Matrix</button>
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) %}
                    <button id="tab-btn-buddy" onclick="switchQualiTab('buddy')" class="qual-tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Buddy &amp; Teams</button>
                    {% endif %}
                </nav>
            </div>
        </div>

        <!-- Panel: Übersicht -->
        <div id="panel-overview" class="p-4 sm:p-6 lg:p-8">
            <div id="qualifications-tile">
                    {% if grouped_qualifications %}
                    <div class="bg-white rounded-xl border border-slate-200">
                    <!-- Gruppen-Filter + Suche -->
                    <div class="flex flex-wrap items-center gap-2 px-4 pt-4 pb-3 border-b border-slate-200">
                        <span class="text-sm text-slate-500">Gruppe:</span>
                        <button id="quali-filter-all" onclick="setQualiGroupFilter(null)" class="px-3 py-1 rounded-full text-xs font-medium text-white" style="background:#0c4a6e;">Alle</button>
                        {% for group_name in grouped_qualifications %}
                        <button onclick="setQualiGroupFilter('{{ group_name }}')"
                                id="quali-filter-{{ loop.index }}"
                                data-group="{{ group_name }}"
                                class="quali-filter-btn px-3 py-1 rounded-full text-xs font-medium border border-slate-300 text-slate-600 hover:bg-slate-100">
                            {{ group_name }}
                        </button>
                        {% endfor %}
                        <div class="ml-auto flex items-center gap-2">
                            <div id="quali-search-wrap" class="flex items-center gap-1 bg-white ring-1 ring-slate-200 rounded-full px-3 py-1.5 shadow-sm">
                                <span class="material-icons-outlined text-slate-400 text-base">search</span>
                                <input id="qualificationPageSearchInput" type="text" placeholder="Mitarbeiter oder ID suchen…" class="text-sm bg-transparent outline-none w-48 text-slate-700 placeholder-slate-400">
                                <button id="qualSearchClear" onclick="document.getElementById('qualificationPageSearchInput').value='';document.getElementById('qualificationPageSearchInput').dispatchEvent(new Event('input'));this.classList.add('hidden')" class="hidden text-slate-400 hover:text-slate-600 leading-none">&times;</button>
                            </div>
                            {% if session.get('is_admin') %}
                            <button onclick="openMerkmaleModal()" title="Merkmale verwalten" class="flex items-center justify-center w-8 h-8 rounded-full text-white shadow-sm hover:opacity-80 transition-opacity" style="background:#0c4a6e;">
                                <span class="material-icons-outlined text-base leading-none">add</span>
                            </button>
                            {% endif %}
                        </div>
                    </div>
                    <div class="p-4">
                        <div class="overflow-x-auto">
                        <table class="qual-table qualification-table">
                            <thead>
                                <tr>
                                    <th class="sticky-col-name col-name">Name</th>
                                    <th class="sticky-col-id">ID</th>
                                    {% for merkmal_name in merkmale_in_order %}
                                        <th style="min-width:72px;">
                                            {% if '/' in merkmal_name %}
                                                {% set parts = merkmal_name.split('/', 1) %}
                                                {{ parts[0] }}/<br>{{ parts[1] }}
                                            {% else %}
                                                {{ merkmal_name }}
                                            {% endif %}
                                        </th>
                                    {% endfor %}
                                </tr>
                            </thead>
                            <tbody>
                                {% for group_name, qualifications_in_group in grouped_qualifications.items() %}
                                <tr class="qual-group-row qualification-group" data-group-name="{{ group_name }}" style="background:#0c4a6e11;border-bottom-color:#0c4a6e44;">
                                    <td colspan="2" style="position:sticky;left:0;z-index:4;color:#0c4a6e;background:#0c4a6e11;white-space:nowrap;">
                                        {{ group_name }} &mdash; {{ qualifications_in_group|length }} Mitarbeiter
                                    </td>
                                    {% for _ in merkmale_in_order %}<td style="background:#0c4a6e11;border-color:#e2e8f0;"></td>{% endfor %}
                                </tr>
                                {% for qual_entry in qualifications_in_group %}
                                <tr data-id="{{ qual_entry.id }}" data-name="{{ qual_entry.name }}" class="employee-row">
                                    <td class="sticky-col-name" style="font-weight:500;">{{ qual_entry.name }}</td>
                                    <td class="sticky-col-id" style="text-align:center;color:#64748b;">{{ qual_entry.id.upper() }}</td>
                                    {% for merkmal_name_iter in merkmale_in_order %}
                                        {% set merkmal_data = namespace(wert='', start='', end='', cert='') %}
                                        {% for m_item in qual_entry.merkmale %}
                                            {% if m_item.merkmal == merkmal_name_iter %}
                                                {% set merkmal_data.wert = m_item.wert %}
                                                {% set merkmal_data.start = m_item.start_date %}
                                                {% set merkmal_data.end = m_item.end_date %}
                                                {% set merkmal_data.cert = m_item.cert_path %}
                                            {% endif %}
                                        {% endfor %}
                                        <td class="editable-qual-cell"
                                            data-merkmal="{{ merkmal_name_iter }}"
                                            data-status="{{ merkmal_data.wert|trim|upper }}"
                                            data-start="{{ merkmal_data.start }}"
                                            data-end="{{ merkmal_data.end }}"
                                            data-cert="{{ merkmal_data.cert }}">
                                            <div class="cell-content-wrapper">
                                                {% if merkmal_data.wert and merkmal_data.wert.strip() %}
                                                    <span class="status-dot status-{{ merkmal_data.wert|trim|upper }}" title="{{ merkmal_data.wert }}"></span>
                                                {% else %}
                                                    <span class="status-dot-placeholder" title="Leer"></span>
                                                {% endif %}
                                                {% if merkmal_data.cert %}
                                                    <span class="material-icons-outlined cert-icon" title="Zertifikat">description</span>
                                                {% endif %}
                                            </div>
                                        </td>
                                    {% endfor %}
                                </tr>
                                {% endfor %}
                                {% endfor %}
                            </tbody>
                        </table>
                        </div><!-- /overflow-x-auto -->
                    </div><!-- /p-4 table wrapper -->
                    </div><!-- /white box -->
                    {% else %}
                        <p class="text-center py-10 text-slate-400">Keine Qualifikationsdaten gefunden.</p>
                    {% endif %}
            </div><!-- /qualifications-tile -->
        </div><!-- /panel-overview -->

        {% if session.get('is_admin') %}
        <!-- Modal: Merkmale verwalten -->
        <div id="modal-merkmale" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
            <div class="bg-white rounded-2xl shadow-xl w-full max-w-md mx-4 flex flex-col">
                <div class="flex items-center justify-between px-6 py-4 border-b border-slate-200">
                    <h2 class="text-base font-semibold text-slate-800">Qualifikations-Merkmale</h2>
                    <button onclick="closeMerkmaleModal()" class="text-slate-400 hover:text-slate-600 leading-none">
                        <span class="material-icons-outlined text-xl">close</span>
                    </button>
                </div>
                <div class="px-6 pt-4 pb-2">
                    <ul id="merkmaleListe" class="border border-slate-200 rounded-xl p-1 min-h-[8rem] max-h-72 overflow-y-auto bg-slate-50 space-y-1 mb-2"></ul>
                    <p class="text-xs text-slate-400 mb-4">Ziehe Merkmale um die Reihenfolge zu ändern. Entferne sie mit dem Papierkorb-Symbol.</p>
                    <div class="flex items-center gap-2 border-t border-slate-200 pt-4">
                        <input type="text" id="neuesMerkmalInput" placeholder="Neues Merkmal…" class="flex-1 border border-slate-300 rounded-lg px-3 py-2 text-sm outline-none">
                        <button id="addMerkmalToListBtn" class="px-4 py-2 text-white text-sm font-medium rounded-lg flex items-center gap-1" style="background:#0c4a6e;">
                            <span class="material-icons-outlined text-base">add</span> Hinzufügen
                        </button>
                    </div>
                </div>
                <div class="flex justify-end gap-2 px-6 py-4 border-t border-slate-200">
                    <button onclick="closeMerkmaleModal()" class="px-4 py-2 text-sm text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50">Abbrechen</button>
                    <button id="saveMerkmaleBtn" class="px-5 py-2 text-white text-sm font-semibold rounded-lg shadow-sm" style="background:#0c4a6e;">Änderungen speichern</button>
                </div>
            </div>
        </div>
        {% endif %}

        <!-- Panel: Kompetenz-Matrix -->
        <div id="panel-matrix" class="hidden p-4 sm:p-6 lg:p-8">
            <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
                <div class="px-6 py-4 border-b border-slate-200 bg-slate-50/50">
                    <h3 class="font-bold text-slate-800">Kompetenz-Matrix</h3>
                </div>
                <div class="overflow-x-auto custom-scrollbar">
                    <table class="w-full text-sm text-left border-collapse" id="qm-skillMatrixTable">
                        <thead class="bg-slate-50 text-xs text-slate-500 uppercase">
                            <tr>
                                <th class="px-6 py-4 font-bold border-b border-r sticky left-0 bg-slate-50 z-10 w-48">Mitarbeiter</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-slate-100 text-slate-700"></tbody>
                    </table>
                </div>
                <div class="p-4 border-t border-slate-200 bg-slate-50 flex gap-4 text-xs">
                    <div class="flex items-center gap-1"><span class="w-3 h-3 rounded-full bg-green-100 border border-green-300"></span> &lt; 3 Monate</div>
                    <div class="flex items-center gap-1"><span class="w-3 h-3 rounded-full bg-yellow-100 border border-yellow-300"></span> &lt; 6 Monate</div>
                    <div class="flex items-center gap-1"><span class="w-3 h-3 rounded-full bg-red-100 border border-red-300"></span> &gt; 6 Monate</div>
                </div>
            </div>
        </div>

        <!-- Panel: Analyse -->
        <div id="panel-analyse" class="hidden p-4 sm:p-6 lg:p-8">
            <div class="grid grid-cols-1 lg:grid-cols-3 xl:grid-cols-4 gap-6">
                <!-- Filter-Spalte -->
                <div class="lg:col-span-1 space-y-4">
                    <div class="bg-white rounded-xl p-5 border border-slate-200 shadow-sm text-slate-700">
                        <h3 class="text-base font-semibold text-slate-800 mb-4">Filter</h3>
                        <div class="space-y-4">
                            <div>
                                <label for="qa-empFilter" class="block text-sm font-medium text-slate-700 mb-1">Mitarbeiter</label>
                                <select id="qa-empFilter" class="qa-select">
                                    <option value="">Alle Mitarbeiter</option>
                                    {% for emp in employees_list %}
                                    <option value="{{ emp.id }}">{{ emp.name }}</option>
                                    {% endfor %}
                                </select>
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-slate-700 mb-1">Gruppen</label>
                                <div class="mt-1 space-y-1 max-h-32 overflow-y-auto p-2 border border-slate-200 rounded-md bg-slate-50 custom-scrollbar">
                                    {% for group in groups_list %}
                                    <label class="flex items-center p-1 hover:bg-white rounded cursor-pointer">
                                        <input type="checkbox" class="h-4 w-4 rounded border-slate-300 qa-group-cb" value="{{ group }}">
                                        <span class="ml-2 text-sm text-slate-600">{{ group }}</span>
                                    </label>
                                    {% endfor %}
                                </div>
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-slate-700 mb-1">Spezifische Skills</label>
                                <div class="mt-1 space-y-1 max-h-32 overflow-y-auto p-2 border border-slate-200 rounded-md bg-slate-50 custom-scrollbar">
                                    {% for skill in skills_list %}
                                    <label class="flex items-center p-1 hover:bg-white rounded cursor-pointer">
                                        <input type="checkbox" class="h-4 w-4 rounded border-slate-300 qa-skill-cb" value="{{ skill }}">
                                        <span class="ml-2 text-sm text-slate-600">{{ skill }}</span>
                                    </label>
                                    {% endfor %}
                                </div>
                            </div>
                            <div id="qa-activeTags" class="pt-2 flex flex-wrap gap-2 border-t border-slate-100"></div>
                            <button id="qa-clearFilters" class="w-full text-sm text-blue-600 hover:underline text-left">Alle Filter zurücksetzen</button>
                        </div>
                    </div>
                    <div id="qa-filterSummary" class="space-y-2 bg-white rounded-xl p-5 border border-slate-200 shadow-sm"></div>
                    <div id="qa-empDetailCard" class="bg-white rounded-xl p-5 border border-slate-200 shadow-sm hidden">
                        <div id="qa-empDetailContent"></div>
                    </div>
                </div>
                <!-- Charts-Spalte -->
                <div class="lg:col-span-2 xl:col-span-3 space-y-6">
                    <div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
                        <div class="xl:col-span-1 bg-white rounded-xl p-5 border border-slate-200 shadow-sm">
                            <h3 class="font-semibold text-slate-800 text-center mb-2 text-sm">Qualifikationsstatus</h3>
                            <div id="qa-trainingChart"></div>
                        </div>
                        <div class="xl:col-span-2 bg-white rounded-xl p-5 border border-slate-200 shadow-sm">
                            <h3 class="font-semibold text-slate-800 mb-2 text-sm">Triebwerkstypen</h3>
                            <div id="qa-skillChart"></div>
                        </div>
                    </div>
                    <div class="bg-white rounded-xl p-5 border border-slate-200 shadow-sm">
                        <div class="flex justify-between items-center mb-2">
                            <h3 class="font-semibold text-slate-800 text-sm">Verfügbarkeitstrend pro Skill</h3>
                            <select id="qa-availTimeFilter" class="qa-select" style="width:auto;font-size:0.75rem;padding:0.25rem 0.5rem;">
                                <option value="14">Nächste 14 Tage</option>
                                <option value="7">Nächste 7 Tage</option>
                                <option value="30">Nächste 30 Tage</option>
                            </select>
                        </div>
                        <div id="qa-availChart"></div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Panel: Buddy & Teams -->
        <div id="panel-buddy" class="hidden p-4 sm:p-6 lg:p-8">
            <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
                <div class="px-6 py-4 border-b border-slate-200 bg-slate-50/50">
                    <h3 class="font-bold text-slate-800">Buddy-System &amp; Team-Zuordnung</h3>
                </div>
                <div id="buddy-loading" class="py-10 text-center text-slate-400 text-sm">Lade Daten…</div>
                <table class="w-full text-sm text-left hidden" id="buddy-table">
                    <thead class="bg-slate-50 text-xs text-slate-500 uppercase border-b border-slate-200">
                        <tr>
                            <th class="px-6 py-4 font-bold">Mitarbeiter</th>
                            <th class="px-6 py-4 font-bold">Buddy (Mentor)</th>
                            <th class="px-6 py-4 font-bold">Präferenz-Gruppen (Teams)</th>
                            <th class="px-6 py-4 text-right">Status</th>
                        </tr>
                    </thead>
                    <tbody id="relations-table-body" class="divide-y divide-slate-100"></tbody>
                </table>
            </div>
        </div>

    </main>
</div>
<div id="qualEditModal" class="fixed inset-0 bg-black/40 overflow-y-auto h-full w-full flex items-center justify-center hidden z-50 p-4">
    <div class="relative mx-auto p-6 border w-full max-w-lg shadow-xl rounded-2xl bg-white">
        <div class="flex justify-between items-center pb-4 border-b border-slate-200">
            <h3 id="qualEditModalTitle" class="text-lg leading-6 font-bold text-gray-900">Qualifikation bearbeiten</h3>
            <button id="closeQualEditModalBtn" class="text-gray-400 hover:text-gray-600 p-1 rounded-full">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <div class="mt-5 space-y-5">
            <input type="hidden" id="qualEditUserId">
            <input type="hidden" id="qualEditMerkmalName">

            <div>
                <label for="qualEditStatus" class="block text-sm font-medium text-gray-700 mb-1">Status</label>
                <select id="qualEditStatus" class="form-select-modal rounded-lg"></select>
            </div>
            <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
                <div>
                    <label for="qualEditStartDate" class="block text-sm font-medium text-gray-700 mb-1">Startdatum</label>
                    <input type="date" id="qualEditStartDate" class="form-input-modal rounded-lg">
                </div>
                <div>
                    <label for="qualEditEndDate" class="block text-sm font-medium text-gray-700 mb-1">Abschlussdatum</label>
                    <input type="date" id="qualEditEndDate" class="form-input-modal rounded-lg">
                </div>
            </div>
            <div>
                <label for="qualEditCertFilename" class="block text-sm font-medium text-gray-700 mb-1">Zugeordnetes Zertifikat</label>
                <div class="mt-1 flex">
                    <input type="text" id="qualEditCertFilename" class="form-input-modal rounded-l-lg flex-1 bg-slate-100 min-w-0" readonly placeholder="Kein Zertifikat">
                    <button id="certFileRemoveBtn" type="button" class="relative -ml-px inline-flex items-center border border-gray-300 bg-slate-50 px-3 py-2 text-sm font-medium text-gray-600 hover:bg-slate-100" title="Zuweisung entfernen">
                        <span class="material-icons-outlined text-base">link_off</span>
                    </button>
                    <button id="certDownloadBtn" type="button" class="relative -ml-px hidden items-center border border-gray-300 bg-slate-50 px-3 py-2 text-sm font-medium text-gray-600 hover:bg-slate-100" title="Zertifikat herunterladen">
                        <span class="material-icons-outlined text-base">download</span>
                    </button>
                    <button id="certFilePickerBtn" type="button" class="relative -ml-px inline-flex items-center space-x-2 rounded-r-lg border border-gray-300 bg-slate-50 px-4 py-2 text-sm font-medium text-gray-600 hover:bg-slate-100">
                        <span class="material-icons-outlined text-base">upload_file</span>
                        <span>Hochladen</span>
                    </button>
                </div>
                <input type="file" id="hiddenCertFileInput" class="hidden" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx">
                <p class="mt-2 text-xs text-gray-500">Beim Speichern wird eine neu hochgeladene Datei die alte Zuweisung für dieses Merkmal ersetzen.</p>
            </div>
        </div>
        <div class="mt-6 pt-5 flex justify-end space-x-3 border-t border-slate-200">
            <button id="cancelQualEditBtn" type="button" class="px-5 py-2.5 bg-slate-100 text-slate-700 text-sm font-semibold rounded-lg shadow-sm hover:bg-slate-200">Abbrechen</button>
            <button id="saveQualChangesBtn" type="button" class="px-5 py-2.5 text-white text-sm font-semibold rounded-lg shadow-sm disabled:opacity-50" style="background:#0c4a6e;">Speichern</button>
        </div>
    </div>
</div>


<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        // --- Logik für die einklappbare Sidebar ---
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    toggleBtnIcon.textContent = 'chevron_left';
                }
            };
            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
        }

        // --- Logik für die Qualifikationstabelle ---
        const statusOptions = [
            { value: '', text: '- Leer -' }, { value: 'X', text: 'X (Abgeschlossen)' },
            { value: 'TS', text: 'TS (Gestartet)' }, { value: 'TT', text: 'TT (Überfällig)' },
            { value: 'TP', text: 'TP (Geplant)' }
        ];

        const qualificationPageSearchInput = document.getElementById('qualificationPageSearchInput');
        const qualSearchClear = document.getElementById('qualSearchClear');
        if(qualificationPageSearchInput) {
            qualificationPageSearchInput.addEventListener('input', (event) => {
                if (qualSearchClear) qualSearchClear.classList.toggle('hidden', !event.target.value);
                const searchTerm = event.target.value.toLowerCase();
                const tbody = document.querySelector('table.qualification-table tbody');
                if (!tbody) return;
                let lastGroupRow = null;
                let groupHasMatch = false;
                Array.from(tbody.rows).forEach(row => {
                    if (row.classList.contains('qual-group-row')) {
                        if (lastGroupRow) lastGroupRow.style.display = groupHasMatch || !searchTerm ? '' : 'none';
                        lastGroupRow = row;
                        groupHasMatch = false;
                    } else if (row.classList.contains('employee-row')) {
                        const name = (row.cells[0] && row.cells[0].textContent.toLowerCase()) || '';
                        const id   = (row.cells[1] && row.cells[1].textContent.toLowerCase()) || '';
                        const match = !searchTerm || name.includes(searchTerm) || id.includes(searchTerm);
                        row.style.display = match ? '' : 'none';
                        if (match) groupHasMatch = true;
                    }
                });
                if (lastGroupRow) lastGroupRow.style.display = groupHasMatch || !searchTerm ? '' : 'none';
            });
        }

        const qualificationsTile = document.getElementById('qualifications-tile'); 
        const qualEditModal = document.getElementById('qualEditModal');
        const closeQualEditModalBtn = document.getElementById('closeQualEditModalBtn');
        const cancelQualEditBtn = document.getElementById('cancelQualEditBtn');
        const saveQualChangesBtn = document.getElementById('saveQualChangesBtn');
        const qualEditModalTitle = document.getElementById('qualEditModalTitle');
        const qualEditStatusSelect = document.getElementById('qualEditStatus');
        const qualEditStartDate = document.getElementById('qualEditStartDate');
        const qualEditEndDate = document.getElementById('qualEditEndDate');
        const qualEditCertFilenameInput = document.getElementById('qualEditCertFilename');
        const certFilePickerBtn = document.getElementById('certFilePickerBtn');
        const hiddenCertFileInput = document.getElementById('hiddenCertFileInput');
        const certFileRemoveBtn = document.getElementById('certFileRemoveBtn');
        const certDownloadBtn = document.getElementById('certDownloadBtn');
        let activeEditingCell = null;
        let selectedFile = null;

        if (qualificationsTile) {
            function validateDates() {
                const start = qualEditStartDate.value;
                const end = qualEditEndDate.value;
                let isValid = true;
                qualEditStartDate.classList.remove('is-invalid');
                qualEditEndDate.classList.remove('is-invalid');
                if (start && end && new Date(end) < new Date(start)) {
                    qualEditEndDate.classList.add('is-invalid');
                    qualEditEndDate.title = "Das Abschlussdatum darf nicht vor dem Startdatum liegen.";
                    isValid = false;
                } else {
                    qualEditEndDate.title = "";
                }
                saveQualChangesBtn.disabled = !isValid;
            }

            function openQualiEditModal(cell) {
                activeEditingCell = cell;
                const row = cell.closest('tr');
                const userId = row.dataset.id;
                const userName = row.dataset.name;
                const merkmalName = cell.dataset.merkmal;

                qualEditModalTitle.textContent = `"${merkmalName}" für ${userName} bearbeiten`;
                document.getElementById('qualEditUserId').value = userId;
                document.getElementById('qualEditMerkmalName').value = merkmalName;

                qualEditStatusSelect.innerHTML = '';
                statusOptions.forEach(opt => {
                    const option = document.createElement('option');
                    option.value = opt.value;
                    option.textContent = opt.text;
                    if (opt.value === cell.dataset.status) option.selected = true;
                    qualEditStatusSelect.appendChild(option);
                });

                qualEditStartDate.value = cell.dataset.start || '';
                qualEditEndDate.value = cell.dataset.end || '';

                const currentFilename = cell.dataset.cert || '';
                qualEditCertFilenameInput.value = currentFilename;
                hiddenCertFileInput.value = '';
                selectedFile = null;

                certDownloadBtn.classList.toggle('hidden', !currentFilename);

                validateDates();
                qualEditStartDate.addEventListener('change', validateDates);
                qualEditEndDate.addEventListener('change', validateDates);
                qualEditModal.classList.remove('hidden');
            }

            function closeQualiEditModal() {
                qualEditStartDate.removeEventListener('change', validateDates);
                qualEditEndDate.removeEventListener('change', validateDates);
                qualEditModal.classList.add('hidden');
                activeEditingCell = null;
            }

            async function saveQualiChanges() {
                if (!activeEditingCell) return;
                saveQualChangesBtn.disabled = true;
                let finalCertFilename = qualEditCertFilenameInput.value;
                 if (finalCertFilename.startsWith('Wird hochgeladen: ')) {
                    finalCertFilename = ''; // Klarer Zustand, wenn nur Upload relevant ist
                }

                if (selectedFile) {
                    const uploadFormData = new FormData();
                    uploadFormData.append('certificate_file', selectedFile);
                    uploadFormData.append('employee_id', document.getElementById('qualEditUserId').value);
                    uploadFormData.append('merkmal_name', document.getElementById('qualEditMerkmalName').value);
                    try {
                        const uploadResponse = await fetch("{{ url_for('api_upload_certificate') }}", { method: 'POST', body: uploadFormData });
                        const uploadResult = await uploadResponse.json();
                        if (!uploadResponse.ok) throw new Error(uploadResult.error || 'Fehler beim Upload');
                        finalCertFilename = uploadResult.filename;
                    } catch (error) {
                        alert('Fehler beim Datei-Upload: ' + error.message);
                        saveQualChangesBtn.disabled = false;
                        return;
                    }
                }

                const payload = {
                    id: document.getElementById('qualEditUserId').value,
                    merkmal: document.getElementById('qualEditMerkmalName').value,
                    new_value: document.getElementById('qualEditStatus').value,
                    start_date: document.getElementById('qualEditStartDate').value,
                    end_date: document.getElementById('qualEditEndDate').value,
                    cert_path: finalCertFilename
                };

                try {
                    const response = await fetch("{{ url_for('api_update_qualification_status') }}", {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify(payload)
                    });
                    const result = await response.json();
                    if (!response.ok) throw new Error(result.error || 'Speichern fehlgeschlagen.');

                    activeEditingCell.dataset.status = payload.new_value;
                    activeEditingCell.dataset.start = payload.start_date;
                    activeEditingCell.dataset.end = payload.end_date;
                    activeEditingCell.dataset.cert = payload.cert_path;

                    const displaySpan = activeEditingCell.querySelector('.qual-display');
                    displaySpan.innerHTML = payload.new_value ? `<span class="status-dot status-${payload.new_value.toUpperCase()}" title="${payload.new_value}"></span>` : '<span class="status-dot-placeholder" title="- Leer -"></span>';

                    let certIcon = activeEditingCell.querySelector('.cert-icon');
                    if (payload.cert_path && !certIcon) {
                        certIcon = document.createElement('span');
                        certIcon.className = 'material-icons-outlined cert-icon';
                        certIcon.title = 'Zertifikat vorhanden';
                        certIcon.textContent = 'description';
                        activeEditingCell.querySelector('.cell-content-wrapper').appendChild(certIcon);
                    } else if (!payload.cert_path && certIcon) {
                        certIcon.remove();
                    }
                    closeQualiEditModal();
                } catch (error) {
                    alert('Fehler beim Speichern der Qualifikation: ' + error.message);
                } finally {
                    saveQualChangesBtn.disabled = false;
                }
            }

            certFilePickerBtn.addEventListener('click', () => { hiddenCertFileInput.click(); });
            hiddenCertFileInput.addEventListener('change', (event) => {
                if (event.target.files && event.target.files.length > 0) {
                    selectedFile = event.target.files[0];
                    qualEditCertFilenameInput.value = `Wird hochgeladen: ${selectedFile.name}`;
                    certDownloadBtn.classList.add('hidden');
                }
            });
            certFileRemoveBtn.addEventListener('click', () => {
                qualEditCertFilenameInput.value = '';
                hiddenCertFileInput.value = '';
                selectedFile = null;
                certDownloadBtn.classList.add('hidden');
            });
            certDownloadBtn.addEventListener('click', () => {
                const employeeId = document.getElementById('qualEditUserId').value;
                const filename = qualEditCertFilenameInput.value;
                if(employeeId && filename && !filename.startsWith('Wird hochgeladen:')) {
                    window.open(`/download_certificate/${employeeId}/${filename}`, '_blank');
                }
            });

            qualificationsTile.addEventListener('click', function(event) {
                const cell = event.target.closest('.editable-qual-cell');
                const certIcon = event.target.closest('.cert-icon');
                if (certIcon && cell) {
                    event.stopPropagation();
                    const employeeId = cell.closest('tr').dataset.id;
                    const filename = cell.dataset.cert;
                    if (employeeId && filename) {
                        window.open(`/download_certificate/${employeeId}/${filename}`, '_blank');
                    }
                } else if (cell) {
                    openQualiEditModal(cell);
                }
            });

            closeQualEditModalBtn.addEventListener('click', closeQualiEditModal);
            cancelQualEditBtn.addEventListener('click', closeQualiEditModal);
            saveQualChangesBtn.addEventListener('click', saveQualiChanges);
        }

        const merkmaleListeUl = document.getElementById('merkmaleListe');
        const neuesMerkmalInput = document.getElementById('neuesMerkmalInput');
        const addMerkmalToListBtn = document.getElementById('addMerkmalToListBtn');
        const saveMerkmaleBtn = document.getElementById('saveMerkmaleBtn');
        let currentMerkmaleImModal = [];
        let sortableInstance = null;

        async function fetchAndDisplayMerkmale() {
            try {
                const response = await fetch("{{ url_for('api_get_merkmale') }}"); 
                if (!response.ok) throw new Error('Netzwerkantwort war nicht ok beim Laden der Merkmale.');
                const merkmaleFromServer = await response.json();
                currentMerkmaleImModal = [...merkmaleFromServer];
                renderMerkmaleListe();
            } catch (error) {
                console.error("Fehler beim Laden der Merkmale:", error);
                merkmaleListeUl.innerHTML = '<li class="p-2 text-red-600">Fehler: Merkmale konnten nicht geladen werden.</li>';
            }
        }

        function renderMerkmaleListe() {
            merkmaleListeUl.innerHTML = '';
            currentMerkmaleImModal.forEach((merkmalText) => {
                const li = document.createElement('li');
                li.className = 'flex justify-between items-center p-2 bg-white border border-gray-200 rounded hover:bg-gray-100 cursor-grab';
                const textSpan = document.createElement('span');
                textSpan.textContent = merkmalText;
                li.appendChild(textSpan);
                const deleteBtn = document.createElement('button');
                deleteBtn.innerHTML = '<span class="material-icons-outlined text-red-500 hover:text-red-700 text-lg leading-none">delete_outline</span>';
                deleteBtn.className = 'p-1 rounded hover:bg-red-100';
                deleteBtn.title = "Merkmal entfernen";
                deleteBtn.onclick = (e) => {
                    e.stopPropagation(); 
                    currentMerkmaleImModal = currentMerkmaleImModal.filter(m => m !== merkmalText);
                    renderMerkmaleListe();
                };
                li.appendChild(deleteBtn);
                merkmaleListeUl.appendChild(li);
            });
            if (sortableInstance) sortableInstance.destroy();
            if (merkmaleListeUl.children.length > 0 && typeof Sortable !== 'undefined') {
                 sortableInstance = Sortable.create(merkmaleListeUl, {
                    animation: 150,
                    ghostClass: 'bg-blue-100',
                    onEnd: function (evt) {
                        const movedItem = currentMerkmaleImModal.splice(evt.oldDraggableIndex, 1)[0];
                        currentMerkmaleImModal.splice(evt.newDraggableIndex, 0, movedItem);
                    }
                });
            }
        }

        // Tab-Switching
        function switchQualiTab(tab) {
            const panels = { overview: 'panel-overview', analyse: 'panel-analyse', matrix: 'panel-matrix', buddy: 'panel-buddy' };
            const btns   = { overview: 'tab-btn-overview', analyse: 'tab-btn-analyse', matrix: 'tab-btn-matrix', buddy: 'tab-btn-buddy' };
            Object.keys(panels).forEach(function(t) {
                const p = document.getElementById(panels[t]);
                const b = document.getElementById(btns[t]);
                if (p) p.classList.toggle('hidden', t !== tab);
                if (b) {
                    b.className = 'qual-tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium ' +
                        (t === tab ? 'border-primary text-primary' : 'border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700');
                }
            });
            const searchWrap = document.getElementById('quali-search-wrap');
            if (searchWrap) searchWrap.style.display = tab === 'overview' ? '' : 'none';
            if (tab === 'analyse') initQaCharts();
            if (tab === 'matrix') loadQmMatrix();
            if (tab === 'buddy') loadBuddyRelations();
        }
        window.switchQualiTab = switchQualiTab;

        function openMerkmaleModal() {
            document.getElementById('modal-merkmale').classList.remove('hidden');
            fetchAndDisplayMerkmale();
        }
        function closeMerkmaleModal() {
            document.getElementById('modal-merkmale').classList.add('hidden');
        }
        window.openMerkmaleModal = openMerkmaleModal;
        window.closeMerkmaleModal = closeMerkmaleModal;

        // Gruppen-Filter
        let activeQualiGroup = null;
        function setQualiGroupFilter(groupName) {
            activeQualiGroup = groupName;
            // Pill-Styles
            document.getElementById('quali-filter-all').style.background = groupName === null ? '#0c4a6e' : '';
            document.getElementById('quali-filter-all').className = 'px-3 py-1 rounded-full text-xs font-medium ' +
                (groupName === null ? 'text-white' : 'bg-slate-100 text-slate-600 hover:bg-slate-200');
            document.querySelectorAll('.quali-filter-btn').forEach(function(btn) {
                const active = btn.dataset.group === groupName;
                btn.className = 'quali-filter-btn px-3 py-1 rounded-full text-xs font-medium ' +
                    (active ? 'text-white' : 'border border-slate-300 text-slate-600 hover:bg-slate-100');
                btn.style.background = active ? '#0c4a6e' : '';
            });
            // Zeilen ein-/ausblenden
            const tbody = document.querySelector('table.qualification-table tbody');
            if (!tbody) return;
            let show = groupName === null;
            Array.from(tbody.rows).forEach(function(row) {
                if (row.classList.contains('qual-group-row')) {
                    show = groupName === null || row.dataset.groupName === groupName;
                    row.style.display = show ? '' : 'none';
                } else if (row.classList.contains('employee-row')) {
                    row.style.display = show ? '' : 'none';
                }
            });
        }
        window.setQualiGroupFilter = setQualiGroupFilter;

        if (addMerkmalToListBtn) {
            addMerkmalToListBtn.addEventListener('click', () => {
                const neuesMerkmal = neuesMerkmalInput.value.trim();
                if (neuesMerkmal && !currentMerkmaleImModal.some(m => m.toLowerCase() === neuesMerkmal.toLowerCase())) {
                    currentMerkmaleImModal.push(neuesMerkmal);
                    renderMerkmaleListe();
                    neuesMerkmalInput.value = '';
                    neuesMerkmalInput.focus();
                } else if (currentMerkmaleImModal.some(m => m.toLowerCase() === neuesMerkmal.toLowerCase())) {
                    alert("Dieses Merkmal existiert bereits.");
                } else {
                    alert("Bitte geben Sie einen Namen für das neue Merkmal ein.");
                }
            });
        }

        // --- ANALYSE TAB ---
        let qaChartsInit = false;
        let qaTrainingChart, qaSkillChart, qaAvailChart;
        const qaAllEmployees = {{ full_employee_data | tojson }};
        const qaAllQualifications = {{ full_qualification_data | tojson }};

        function initQaCharts() {
            if (qaChartsInit) return;
            qaChartsInit = true;
            const gOpts = {
                chart: { fontFamily: 'Inter, sans-serif', toolbar: { show: false }, animations: { speed: 400 } },
                dataLabels: { enabled: false },
                legend: { position: 'bottom', fontSize: '12px', markers: { width: 10, height: 10, radius: 10 }, itemMargin: { horizontal: 10 } }
            };
            const statusLabels = ['Abgeschlossen', 'Gestartet', 'Überfällig', 'Geplant'];
            const statusColors = ['#10B981', '#F59E0B', '#EF4444', '#6B7280'];
            qaTrainingChart = new ApexCharts(document.querySelector('#qa-trainingChart'), {
                ...gOpts, series: [], labels: statusLabels, colors: statusColors,
                chart: { ...gOpts.chart, type: 'donut' },
                plotOptions: { pie: { donut: { labels: { show: true, total: { show: true, label: 'Merkmale' } } } } },
                noData: { text: 'Keine Daten für die aktuelle Filterauswahl.' }
            });
            qaTrainingChart.render();
            qaSkillChart = new ApexCharts(document.querySelector('#qa-skillChart'), {
                ...gOpts, series: [], chart: { ...gOpts.chart, type: 'bar', height: 250, stacked: true },
                plotOptions: { bar: { horizontal: true, borderRadius: 4 } },
                xaxis: { categories: [], labels: { show: false }, axisBorder: { show: false }, axisTicks: { show: false } },
                yaxis: { labels: { show: true, style: { fontSize: '11px' } } },
                grid: { show: false },
                legend: { position: 'top', horizontalAlign: 'left' },
                noData: { text: 'Keine Daten zum Anzeigen.' }
            });
            qaSkillChart.render();
            qaAvailChart = new ApexCharts(document.querySelector('#qa-availChart'), {
                ...gOpts, series: [], chart: { ...gOpts.chart, type: 'area', height: 300, zoom: { enabled: false } },
                stroke: { width: 3, curve: 'smooth' },
                xaxis: { type: 'datetime', labels: { format: 'dd MMM' } },
                yaxis: { title: { text: 'Verfügbare Mitarbeiter', style: { fontWeight: 500, color: '#6b7280' } } },
                noData: { text: 'Bitte einen Skill-Typ filtern, um den Trend anzuzeigen.' }
            });
            qaAvailChart.render();
            document.getElementById('qa-empFilter').addEventListener('change', qaApplyFilters);
            document.querySelectorAll('.qa-group-cb').forEach(cb => cb.addEventListener('change', qaApplyFilters));
            document.querySelectorAll('.qa-skill-cb').forEach(cb => cb.addEventListener('change', qaApplyFilters));
            document.getElementById('qa-availTimeFilter').addEventListener('change', qaApplyFilters);
            document.getElementById('qa-clearFilters').addEventListener('click', qaClearFilters);
            qaApplyFilters();
        }

        function qaApplyFilters() {
            const empId = document.getElementById('qa-empFilter').value;
            const selGroups = Array.from(document.querySelectorAll('.qa-group-cb:checked')).map(cb => cb.value);
            const selSkills = Array.from(document.querySelectorAll('.qa-skill-cb:checked')).map(cb => cb.value);
            let filteredQuals;
            if (empId) {
                filteredQuals = qaAllQualifications.filter(q => q.id.toLowerCase() === empId.toLowerCase());
                document.querySelectorAll('.qa-group-cb').forEach(cb => { cb.checked = false; cb.disabled = true; });
                document.querySelectorAll('.qa-skill-cb').forEach(cb => { cb.checked = false; cb.disabled = true; });
                qaShowEmpDetails(empId);
            } else {
                document.querySelectorAll('.qa-group-cb').forEach(cb => cb.disabled = false);
                document.querySelectorAll('.qa-skill-cb').forEach(cb => cb.disabled = false);
                document.getElementById('qa-empDetailCard').classList.add('hidden');
                let tmp = qaAllQualifications;
                if (selGroups.length > 0) tmp = tmp.filter(q => selGroups.includes(q.quali));
                if (selSkills.length > 0) tmp = tmp.filter(q => q.merkmale.some(m => m.merkmal.includes('/') && selSkills.includes(m.merkmal.split('/')[1]) && m.wert));
                filteredQuals = tmp;
            }
            qaUpdateTrainingChart(filteredQuals);
            qaUpdateSkillChart(filteredQuals, selSkills, { isSingleEmployee: !!empId, isGroupOnly: selGroups.length > 0 && selSkills.length === 0 });
            qaUpdateAvailChart(filteredQuals, selSkills);
            qaUpdateFilterSummary(filteredQuals);
            qaUpdateActiveTags(selGroups, selSkills);
        }

        function qaShowEmpDetails(empId) {
            const qd = qaAllQualifications.find(q => q.id.toLowerCase() === empId.toLowerCase());
            if (!qd) return;
            const done = qd.merkmale.filter(m => m.wert === 'X').map(m => m.merkmal).sort();
            const wip = qd.merkmale.filter(m => ['TS','TT','TP'].includes(m.wert)).map(m => m.merkmal).sort();
            let html = `<h3 class="text-base font-semibold text-slate-800 mb-3">${qd.name}</h3>`;
            html += `<h4 class="text-sm font-semibold text-green-600">Abgeschlossen (${done.length})</h4>`;
            html += done.length ? '<ul class="list-disc list-inside text-sm text-slate-600 mt-1 space-y-0.5">' + done.map(s => `<li>${s}</li>`).join('') + '</ul>' : '<p class="text-sm text-slate-400 italic mt-1">Keine</p>';
            html += `<h4 class="text-sm font-semibold text-amber-600 mt-4">In Arbeit (${wip.length})</h4>`;
            html += wip.length ? '<ul class="list-disc list-inside text-sm text-slate-600 mt-1 space-y-0.5">' + wip.map(s => `<li>${s}</li>`).join('') + '</ul>' : '<p class="text-sm text-slate-400 italic mt-1">Keine</p>';
            document.getElementById('qa-empDetailContent').innerHTML = html;
            document.getElementById('qa-empDetailCard').classList.remove('hidden');
        }

        function qaUpdateFilterSummary(filteredQuals) {
            const el = document.getElementById('qa-filterSummary');
            if (document.getElementById('qa-empFilter').value) { el.innerHTML = ''; return; }
            let total = filteredQuals.length, skillTotal = 0, skillCert = 0;
            filteredQuals.forEach(q => q.merkmale.forEach(m => { if (m.merkmal.includes('/') && m.wert) { skillTotal++; if (m.wert === 'X') skillCert++; } }));
            const rate = skillTotal > 0 ? Math.round(skillCert / skillTotal * 100) : 0;
            el.innerHTML = `
                <div class="flex items-center gap-3 py-2"><div class="p-2.5 bg-blue-50 rounded-full"><span class="material-icons-outlined text-blue-600 text-xl">person_search</span></div><div><p class="text-xs text-slate-500">Gefundene Mitarbeiter</p><p class="text-xl font-bold text-slate-800">${total}</p></div></div>
                <div class="flex items-center gap-3 py-2"><div class="p-2.5 bg-yellow-50 rounded-full"><span class="material-icons-outlined text-yellow-600 text-xl">military_tech</span></div><div><p class="text-xs text-slate-500">Zertifizierungsrate</p><p class="text-xl font-bold text-slate-800">${rate}%</p></div></div>
            `;
        }

        function qaUpdateActiveTags(groups, skills) {
            const el = document.getElementById('qa-activeTags');
            el.innerHTML = '';
            groups.forEach(g => { const t = document.createElement('span'); t.className = 'inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800'; t.textContent = g; el.appendChild(t); });
            skills.forEach(s => { const t = document.createElement('span'); t.className = 'inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800'; t.textContent = s; el.appendChild(t); });
        }

        function qaUpdateTrainingChart(quals) {
            if (!qaTrainingChart) return;
            if (!quals.length) { qaTrainingChart.updateSeries([]); return; }
            const counts = { 'X': 0, 'TS': 0, 'TT': 0, 'TP': 0 };
            quals.forEach(q => q.merkmale.forEach(m => { if (m.merkmal.includes('/') && counts.hasOwnProperty(m.wert)) counts[m.wert]++; }));
            qaTrainingChart.updateSeries(['X','TS','TT','TP'].map(k => counts[k]));
        }

        function qaUpdateSkillChart(quals, selSkills, ctx) {
            if (!qaSkillChart) return;
            const engineData = {};
            quals.forEach(q => q.merkmale.forEach(m => {
                if (!m.merkmal.includes('/') || !m.wert) return;
                const parts = m.merkmal.split('/');
                const engine = parts[0], skill = parts[1];
                if (selSkills.length > 0 && !selSkills.includes(skill)) return;
                if (!engineData[engine]) engineData[engine] = {};
                if (!engineData[engine][m.wert]) engineData[engine][m.wert] = { count: 0, names: new Set() };
                engineData[engine][m.wert].names.add(q.name);
            }));
            Object.values(engineData).forEach(statuses => Object.values(statuses).forEach(d => { d.count = d.names.size; }));
            const cats = Object.keys(engineData).sort();
            if (!cats.length) {
                qaSkillChart.updateSeries([], false);
                qaSkillChart.updateOptions({ xaxis: { categories: [] }, noData: { text: (ctx.isSingleEmployee || ctx.isGroupOnly) ? '' : 'Keine passenden Skills gefunden.' } });
                return;
            }
            const statuses = ['X','TS','TT','TP'];
            const sLabels = { 'X':'Abgeschlossen','TS':'Gestartet','TT':'Überfällig','TP':'Geplant' };
            const sColors = { 'X':'#10B981','TS':'#F59E0B','TT':'#F05252','TP':'#6B7280' };
            qaSkillChart.updateOptions({
                series: statuses.map(s => ({ name: sLabels[s], data: cats.map(e => engineData[e]?.[s]?.count || 0) })),
                xaxis: { categories: cats },
                colors: statuses.map(s => sColors[s]),
                noData: { text: 'Keine Daten zum Anzeigen.' },
                tooltip: { custom: function({ seriesIndex, dataPointIndex, w }) {
                    const eng = w.globals.labels[dataPointIndex];
                    const sk = statuses[seriesIndex];
                    const d = engineData[eng]?.[sk];
                    if (!d || !d.count) return '';
                    const namesArr = Array.from(d.names);
                    const names = namesArr.slice(0, 10).map(n => `<li>${n}</li>`).join('');
                    const more = namesArr.length > 10 ? `<li>... und ${namesArr.length - 10} weitere</li>` : '';
                    return `<div class="p-2 apexcharts-tooltip-custom"><div class="font-bold border-b pb-1 mb-1">${eng} – ${sLabels[sk]}: ${d.count}</div><ul class="list-disc pl-4 text-xs">${names}${more}</ul></div>`;
                }}
            });
        }

        function qaUpdateAvailChart(filteredQuals, selSkills) {
            if (!qaAvailChart) return;
            const availMap = new Map();
            qaAllEmployees.forEach(emp => {
                const ea = new Map();
                (emp.weeks || []).forEach(week => {
                    const shift = week.Schicht === 'F' ? 'AM' : (week.Schicht === 'S' ? 'PM' : null);
                    if (shift) (week.days || []).forEach(day => { if (day.date && !day.ereignis.trim()) ea.set(`${day.date}_${shift}`, true); });
                });
                availMap.set(emp.id.toLowerCase(), ea);
            });
            const numDays = parseInt(document.getElementById('qa-availTimeFilter').value, 10);
            const today = new Date(); today.setUTCHours(0, 0, 0, 0);
            const slots = [];
            for (let idx = 0; idx < numDays; idx++) {
                const ts = today.getTime() + idx * 86400000;
                slots.push({ ts, shift: 'AM' }); slots.push({ ts: ts + 43200000, shift: 'PM' });
            }
            let skillsToPlot = new Set();
            if (selSkills.length > 0) {
                filteredQuals.forEach(q => q.merkmale.forEach(m => { if (m.merkmal.includes('/') && selSkills.includes(m.merkmal.split('/')[1])) skillsToPlot.add(m.merkmal); }));
            } else {
                filteredQuals.forEach(q => q.merkmale.forEach(m => { if (m.merkmal.includes('/')) skillsToPlot.add(m.merkmal); }));
            }
            if (!skillsToPlot.size) { qaAvailChart.updateSeries([]); qaAvailChart.updateOptions({ noData: { text: 'Bitte einen Skill auswählen, um den Trend zu sehen.' } }); return; }
            const series = [];
            skillsToPlot.forEach(merkmal => {
                const empIds = new Set(filteredQuals.filter(q => q.merkmale.some(m => m.merkmal === merkmal && ['X','TS','TT'].includes(m.wert))).map(q => q.id.toLowerCase()));
                const pts = slots.map(slot => {
                    const d = new Date(slot.ts);
                    const iso = `${d.getUTCFullYear()}-${String(d.getUTCMonth()+1).padStart(2,'0')}-${String(d.getUTCDate()).padStart(2,'0')}`;
                    let cnt = 0;
                    empIds.forEach(id => { const ea = availMap.get(id); if (ea && ea.has(`${iso}_${slot.shift}`)) cnt++; });
                    return { x: slot.ts, y: cnt };
                });
                if (pts.some(p => p.y > 0)) series.push({ name: merkmal, data: pts });
            });
            qaAvailChart.updateSeries(series);
            qaAvailChart.updateOptions({
                xaxis: { type: 'datetime', labels: { formatter: (v, ts) => { if (!ts) return ''; const d = new Date(ts); return d.toLocaleDateString('de-DE', { weekday: 'short' }) + ' ' + (d.getUTCHours() < 12 ? 'AM' : 'PM'); }, rotate: -45, hideOverlappingLabels: false, trim: true }, tooltip: { enabled: true, formatter: v => { const d = new Date(v); return d.toLocaleDateString('de-DE', { dateStyle: 'medium' }) + (d.getUTCHours() < 12 ? ' Früh' : ' Spät'); } } },
                tooltip: { x: { show: false } },
                noData: { text: 'Keine Verfügbarkeiten für die aktuelle Auswahl.' }
            });
        }

        function qaClearFilters() {
            document.getElementById('qa-empFilter').value = '';
            document.querySelectorAll('.qa-group-cb, .qa-skill-cb').forEach(cb => cb.checked = false);
            qaApplyFilters();
        }

        if (saveMerkmaleBtn) {
            saveMerkmaleBtn.addEventListener('click', async () => {
                const finalMerkmalOrder = [...currentMerkmaleImModal]; 
                try {
                    const response = await fetch("{{ url_for('api_update_merkmale') }}", {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ merkmale: finalMerkmalOrder })
                    });
                    const result = await response.json();
                    if (!response.ok) {
                        throw new Error(result.error || `HTTP-Fehler ${response.status}`);
                    }
                    alert(result.message || "Merkmale erfolgreich gespeichert!");
                    closeMerkmaleModal();
                    location.reload(); 
                } catch (error) {
                    console.error("Fehler beim Speichern der Merkmale:", error);
                    alert("Fehler: " + error.message);
                }
            });
        }
    });

    // --- KOMPETENZ-MATRIX TAB ---
    let qmMatrixLoaded = false;

    async function loadQmMatrix() {
        if (qmMatrixLoaded) return;
        qmMatrixLoaded = true;
        const tbody = document.querySelector('#qm-skillMatrixTable tbody');
        tbody.innerHTML = '<tr><td class="p-4 text-slate-400 italic">Lade Daten...</td></tr>';
        try {
            const res = await fetch('/api/analytics/skill_matrix');
            const data = await res.json();
            renderQmMatrix(data);
        } catch(e) {
            tbody.innerHTML = '<tr><td class="p-4 text-red-500">Fehler beim Laden der Daten.</td></tr>';
            console.error(e);
        }
    }

    function renderQmMatrix(data) {
        const theadRow = document.querySelector('#qm-skillMatrixTable thead tr');
        const tbody = document.querySelector('#qm-skillMatrixTable tbody');
        theadRow.innerHTML = '<th class="px-6 py-4 font-bold border-b border-r sticky left-0 bg-slate-50 z-10 w-48">Mitarbeiter</th>';
        data.engines.forEach(eng => {
            theadRow.innerHTML += `<th class="px-4 py-3 font-semibold border-b text-center min-w-[100px]">${eng}</th>`;
        });
        tbody.innerHTML = '';
        const today = new Date();
        data.matrix.forEach(user => {
            let rowHtml = `<td class="px-6 py-3 font-medium text-slate-800 border-r bg-white sticky left-0">${user.name}</td>`;
            data.engines.forEach(eng => {
                const skillData = user.skills[eng];
                let cellClass = 'bg-slate-50', text = '-', clickAction = '', cursorClass = 'cursor-default';
                if (skillData) {
                    const date = new Date(skillData.last_total);
                    const diffDays = (today - date) / (1000 * 60 * 60 * 24);
                    text = date.toLocaleDateString('de-DE', { month: '2-digit', year: '2-digit' });
                    cellClass = diffDays < 90 ? 'bg-green-100 text-green-800' : diffDays < 180 ? 'bg-yellow-50 text-yellow-800' : 'bg-red-100 text-red-800 font-bold';
                    const taskDataSafe = JSON.stringify(skillData.tasks).replace(/"/g, '&quot;');
                    clickAction = `openQmSkillDetails('${user.name}', '${eng}', ${taskDataSafe})`;
                    cursorClass = 'cursor-pointer hover:brightness-90';
                }
                rowHtml += `<td class="px-4 py-3 text-center border-l border-slate-100 ${cellClass} ${cursorClass}" onclick="${clickAction}">${text}</td>`;
            });
            tbody.innerHTML += `<tr class="hover:brightness-95 transition">${rowHtml}</tr>`;
        });
    }

    function openQmSkillDetails(userName, engine, tasks) {
        document.getElementById('qm-skillDetailTitle').textContent = engine;
        document.getElementById('qm-skillDetailSubtitle').textContent = userName;
        const list = document.getElementById('qm-skillDetailList');
        list.innerHTML = '';
        const today = new Date();
        const sorted = Object.entries(tasks).sort((a, b) => new Date(b[1]) - new Date(a[1]));
        sorted.forEach(([taskName, dateIso]) => {
            const d = new Date(dateIso);
            const diff = Math.round((today - d) / (1000 * 60 * 60 * 24));
            const color = diff > 180 ? 'bg-red-100 text-red-800' : diff > 90 ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800';
            list.innerHTML += `<div class="flex justify-between items-center p-2 rounded border border-slate-100"><span class="font-medium text-sm text-slate-700">${taskName}</span><span class="text-xs px-2 py-0.5 rounded ${color}">${d.toLocaleDateString('de-DE')} (${diff}d)</span></div>`;
        });
        document.getElementById('qm-skillDetailModal').classList.remove('hidden');
    }

    // --- Buddy & Teams ---
    const buddyUsers = {{ users | tojson }};
    let buddyRelations = {};
    let buddyLoaded = false;

    async function loadBuddyRelations() {
        if (buddyLoaded) return;
        try {
            const res = await fetch('/api/training/relations');
            buddyRelations = await res.json();
            buddyLoaded = true;
        } catch(e) { console.error('Buddy-Daten konnten nicht geladen werden:', e); }
        renderBuddyRelations();
    }

    function renderBuddyRelations() {
        const loading = document.getElementById('buddy-loading');
        const table   = document.getElementById('buddy-table');
        const tbody   = document.getElementById('relations-table-body');
        tbody.innerHTML = '';
        buddyUsers.forEach(u => {
            const rel = buddyRelations[u.id] || { buddy: '', groups: [] };
            let buddyOpt = `<option value="">- Kein Buddy -</option>`;
            buddyUsers.forEach(b => {
                if (b.id !== u.id) {
                    const sel = rel.buddy === b.id ? 'selected' : '';
                    buddyOpt += `<option value="${b.id}" ${sel}>${b.nachname}, ${b.vorname}</option>`;
                }
            });
            const groupsVal = Array.isArray(rel.groups) ? rel.groups.join(', ') : (rel.groups || '');
            tbody.innerHTML += `<tr class="hover:bg-slate-50 transition-colors border-b border-slate-100 last:border-0">
                <td class="px-6 py-4 font-medium text-slate-800">${u.nachname}, ${u.vorname}</td>
                <td class="px-6 py-4"><select class="border-slate-300 rounded-md text-sm w-full focus:ring-blue-500 focus:border-blue-500 bg-white py-1.5" onchange="updateBuddyRelation('${u.id}', 'buddy', this.value)">${buddyOpt}</select></td>
                <td class="px-6 py-4"><input type="text" class="border-slate-300 rounded-md text-sm w-full focus:ring-blue-500 focus:border-blue-500 py-1.5" value="${groupsVal}" placeholder="Team A..." onblur="updateBuddyRelation('${u.id}', 'groups', this.value)"></td>
                <td class="px-6 py-4 text-right"><span class="inline-flex items-center text-[10px] text-green-700 bg-green-50 px-2 py-0.5 rounded border border-green-200 uppercase tracking-wider font-bold">Active</span></td>
            </tr>`;
        });
        if (loading) loading.classList.add('hidden');
        if (table)   table.classList.remove('hidden');
    }

    async function updateBuddyRelation(userId, key, value) {
        if (!buddyRelations[userId]) buddyRelations[userId] = { buddy: '', groups: [] };
        if (key === 'groups') buddyRelations[userId].groups = value.split(',').map(s => s.trim()).filter(s => s);
        else buddyRelations[userId][key] = value;
        await fetch('/api/training/relations', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(buddyRelations) });
    }
</script>

<!-- Kompetenz-Matrix Detail-Modal -->
<div id="qm-skillDetailModal" class="fixed inset-0 bg-gray-900 bg-opacity-50 hidden z-50 flex items-center justify-center" onclick="this.classList.add('hidden')">
    <div class="bg-white rounded-lg shadow-xl p-6 max-w-sm w-full" onclick="event.stopPropagation()">
        <h3 class="text-lg font-bold text-slate-800 mb-1" id="qm-skillDetailTitle">Details</h3>
        <p class="text-sm text-slate-500 mb-4" id="qm-skillDetailSubtitle"></p>
        <div class="space-y-2 max-h-80 overflow-y-auto custom-scrollbar" id="qm-skillDetailList"></div>
        <button class="mt-6 w-full py-2 bg-slate-100 text-slate-600 rounded hover:bg-slate-200 text-sm font-medium" onclick="document.getElementById('qm-skillDetailModal').classList.add('hidden')">Schließen</button>
    </div>
</div>

</body></html>
"""

ATTENDANCE_PLANNING_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MBET - Anwesenheitsplanung</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<style>
  .sidebar-link { transition: background-color 0.15s, color 0.15s; }
  .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
  .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
  .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
  .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
  .sidebar-label { transition: opacity 0.2s ease-in-out; }
  .sidebar-separator { display: none; }
  #sidebar { transition: width 0.3s ease-in-out; }
  #sidebar.is-collapsed { width: 5rem; }
  #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
  #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
  #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
  #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
  .status-cell { min-width: 44px; height: 36px; cursor: pointer; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 11px; font-weight: 600; display: flex; align-items: center; justify-content: center; user-select: none; transition: opacity 0.1s; }
  .status-cell.readonly { cursor: default; }
  .status-redacted { background: #f1f5f9; color: #94a3b8; }
  .status-cell:hover { opacity: 0.8; }
  .status-cell.holiday { background: #fef2f2 !important; color: #fca5a5 !important; cursor: not-allowed; }
  .status-empty { background: #f8fafc; color: #64748b; }
  .status-U { background: #dcfce7; color: #16a34a; }
  .status-K { background: #fee2e2; color: #dc2626; }
  .status-TZ { background: #fef9c3; color: #ca8a04; }
  .status-G { background: #dbeafe; color: #2563eb; }
  .status-Xs { background: #f3e8ff; color: #9333ea; }
  .status-D { background: #ffedd5; color: #ea580c; }
  .status-we-off { background: #f1f5f9; color: #cbd5e1; cursor: pointer; }
  .status-we-work { background: #f0fdf4; color: #16a34a; cursor: pointer; font-weight: 700; }
  .shift-badge { font-size: 10px; padding: 1px 5px; border-radius: 3px; cursor: pointer; font-weight: 700; }
  .shift-F { background: #dbeafe; color: #1d4ed8; }
  .shift-S { background: #fef3c7; color: #d97706; }
  .shift-N { background: #f0fdf4; color: #15803d; }
  .shift-empty { background: #f1f5f9; color: #64748b; }
  .shift-projected { opacity: 0.4; font-style: italic; outline: 1px dashed currentColor; outline-offset: 1px; }
  .today-col { background: #fef9c3 !important; }
  .sticky-col { position: sticky; left: 0; z-index: 10; background: white; }
  .sticky-col2 { position: sticky; left: 180px; z-index: 10; background: white; }
  .table-scroll { overflow-x: auto; }
  #toast { position: fixed; bottom: 24px; right: 24px; z-index: 9999; transition: all 0.3s; }
  .group-badge { display: inline-block; padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 600; }
  #passwordModal { display: none; }
  .kanban-ghost { opacity: 0.35; background: #dbeafe !important; border: 2px dashed #93c5fd !important; }
  .kanban-dragging { box-shadow: 0 8px 24px rgba(0,0,0,0.15); transform: rotate(1.5deg); cursor: grabbing; }
  .kanban-card { cursor: grab; }
  .kanban-card:active { cursor: grabbing; }
  #kanbanBoard { scrollbar-width: thin; scrollbar-color: #cbd5e1 transparent; }
  #weeksFlyout { transition: opacity 0.2s ease; }
  #weeksChevron { transition: transform 0.2s ease; }
  #saveFab { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9990; display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.25rem; background: #0c4a6e; color: white; border-radius: 9999px; font-size: 0.875rem; font-weight: 600; box-shadow: 0 4px 16px rgba(0,0,0,0.18); cursor: pointer; border: none; transition: background 0.15s, transform 0.1s; }
  #saveFab:hover { background: #075985; transform: translateY(-1px); }
  #saveFab:active { transform: translateY(0); }
  #saveFab:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
  #saveFab.fab-locked { background: #475569; }
  #saveFab.fab-locked:hover { background: #334155; }
  .text-primary { color: #3B82F6; }
  .border-primary { border-color: #3B82F6; }
</style>
</head>
<body>
<div class="flex h-screen bg-white">
{% include 'sidebar.html' %}
<main class="flex-1 overflow-y-auto bg-neutral-50">

<!-- Header -->
<header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
  <h1 class="text-2xl font-bold tracking-tight text-slate-800">Anwesenheitsplanung</h1>
</header>

<div class="p-4 sm:p-6">

<!-- Tab-Nav — frei auf neutral bg -->
<div class="border-b border-slate-200">
  <nav class="-mb-px flex space-x-8">
    <button onclick="switchTab('planning')" id="tab-planning" class="tab-btn whitespace-nowrap border-b-2 px-4 pb-3 pt-2 text-sm font-medium border-primary text-primary">Planung</button>
    {% if can_edit %}
    <button onclick="switchTab('groups')" id="tab-groups" class="tab-btn whitespace-nowrap border-b-2 px-4 pb-3 pt-2 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Gruppen</button>
    <button onclick="switchTab('quotas')" id="tab-quotas" class="tab-btn whitespace-nowrap border-b-2 px-4 pb-3 pt-2 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Urlaubsquoten</button>
    <button onclick="switchTab('holidays')" id="tab-holidays" class="tab-btn whitespace-nowrap border-b-2 px-4 pb-3 pt-2 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Feiertage</button>
    {% endif %}
  </nav>
</div>

<!-- Tab: Planning -->
<div id="panel-planning" class="mt-8">

  <!-- Toolbar (eigene Box) -->
  <div class="flex flex-wrap items-center gap-2 bg-white rounded-xl border border-slate-200 px-4 py-3 mb-4">
    <button onclick="changeWeek(-1)" class="p-1.5 rounded-full bg-white ring-1 ring-slate-200 shadow-sm text-slate-500 hover:bg-slate-50"><span class="material-icons-outlined text-lg">chevron_left</span></button>
    <span id="weekRangeLabel" class="text-sm font-medium text-slate-700 px-1 min-w-[180px] text-center"></span>
    <button onclick="changeWeek(1)" class="p-1.5 rounded-full bg-white ring-1 ring-slate-200 shadow-sm text-slate-500 hover:bg-slate-50"><span class="material-icons-outlined text-lg">chevron_right</span></button>
    <button onclick="jumpToToday()" class="px-3 py-1.5 bg-slate-100 text-slate-700 rounded-full text-sm hover:bg-slate-200 font-medium">Heute</button>
    <div class="w-px h-5 bg-slate-200 mx-1"></div>
    <div class="relative weeks-flyout-wrap" style="display:flex;align-items:center;">
      <button id="weeksTrigger" class="px-3 py-1.5 rounded-full text-sm font-medium text-white flex items-center gap-1 select-none" style="white-space:nowrap;background:#0c4a6e;">
        <span id="weeksTriggerLabel">4W</span>
        <span class="material-icons-outlined" style="font-size:1rem;transition:transform 0.2s;" id="weeksChevron">chevron_right</span>
      </button>
      <div id="weeksFlyout" class="flex items-center gap-1 pl-1" style="width:0;overflow:hidden;opacity:0;transition:width 0.25s ease,opacity 0.2s ease;">
        <button onclick="setNumWeeks(2)"  data-weeks="2"  class="weeks-btn px-3 py-1.5 rounded-full text-sm font-medium bg-slate-100 text-slate-700 hover:bg-slate-200" style="white-space:nowrap;">2W</button>
        <button onclick="setNumWeeks(4)"  data-weeks="4"  class="weeks-btn px-3 py-1.5 rounded-full text-sm font-medium text-white" style="white-space:nowrap;background:#0c4a6e;">4W</button>
        <button onclick="setNumWeeks(8)"  data-weeks="8"  class="weeks-btn px-3 py-1.5 rounded-full text-sm font-medium bg-slate-100 text-slate-700 hover:bg-slate-200" style="white-space:nowrap;">8W</button>
        <button onclick="setNumWeeks(12)" data-weeks="12" class="weeks-btn px-3 py-1.5 rounded-full text-sm font-medium bg-slate-100 text-slate-700 hover:bg-slate-200" style="white-space:nowrap;">12W</button>
      </div>
    </div>
    <div class="ml-auto flex items-center gap-2">
      {% if can_edit %}
      <label class="px-3 py-1.5 bg-white ring-1 ring-slate-200 text-slate-700 rounded-full text-sm hover:bg-slate-50 cursor-pointer font-medium flex items-center gap-1 shadow-sm" title="CSV-Datei importieren (Stammdaten und Schichtplan werden vollständig übernommen)">
        <span class="material-icons-outlined text-base">upload_file</span>CSV
        <input type="file" id="csvUploadInput" accept=".csv" class="hidden" onchange="uploadCsv(this)">
      </label>
      <button onclick="smOpen(null)" class="px-3 py-1.5 bg-white ring-1 ring-slate-200 text-slate-600 rounded-full text-sm hover:bg-slate-50 font-medium shadow-sm flex items-center gap-1" title="Schichtmodell-Bibliothek verwalten"><span class="material-icons-outlined text-base">tune</span>Schichtmodelle</button>
      {% endif %}
      {% if can_edit %}
      <div class="relative" id="legendeWrap">
        <button onclick="document.getElementById('legendePopover').classList.toggle('hidden')" class="p-1.5 rounded-full bg-white ring-1 ring-slate-200 shadow-sm text-slate-500 hover:bg-slate-100 hover:text-slate-700 transition-colors duration-200 select-none" title="Legende">
          <span class="material-icons-outlined text-lg leading-none">info</span>
        </button>
        <div id="legendePopover" class="hidden absolute right-0 top-full mt-2 z-30 bg-white border border-slate-200 rounded-xl shadow-xl p-3 w-56">
          <p class="text-xs font-semibold text-slate-500 mb-2 uppercase tracking-wide">Statusfelder</p>
          <div class="flex flex-col gap-1">
            <span class="status-cell status-U" style="width:auto;justify-content:flex-start;padding:0 8px">U &ndash; Urlaub</span>
            <span class="status-cell status-K" style="width:auto;justify-content:flex-start;padding:0 8px">K &ndash; Krank</span>
            <span class="status-cell status-TZ" style="width:auto;justify-content:flex-start;padding:0 8px">TZ &ndash; Teilzeit</span>
            <span class="status-cell status-G" style="width:auto;justify-content:flex-start;padding:0 8px">G &ndash; Gleittag</span>
            <span class="status-cell status-Xs" style="width:auto;justify-content:flex-start;padding:0 8px">Xs &ndash; Sonderurlaub</span>
            <span class="status-cell status-D" style="width:auto;justify-content:flex-start;padding:0 8px">D &ndash; Dienstreise</span>
          </div>
          <p class="text-xs font-semibold text-slate-500 mt-3 mb-2 uppercase tracking-wide">Spaltenfarben</p>
          <div class="flex flex-col gap-1 text-xs text-slate-600">
            <span class="flex items-center gap-2"><span class="inline-block w-3 h-3 rounded bg-amber-100 border border-amber-400 flex-shrink-0"></span>Unter Mindestbesetzung</span>
            <span class="flex items-center gap-2"><span class="inline-block w-3 h-3 rounded bg-red-100 border border-red-400 flex-shrink-0"></span>Feiertag</span>
            <span class="flex items-center gap-2"><span class="inline-block w-3 h-3 rounded bg-yellow-50 border border-yellow-300 flex-shrink-0"></span>Heute</span>
          </div>
        </div>
      </div>
      {% endif %}
    </div>
  </div>

  <!-- Hauptinhalt (eigene Box) -->
  <div class="bg-white rounded-xl border border-slate-200 p-4">
    <div class="flex flex-wrap gap-2 mb-4 items-center">
      <span class="text-sm text-slate-500">Gruppe:</span>
      <button onclick="setGroupFilter(null)" id="filter-all" class="px-3 py-1 rounded-full text-xs font-medium text-white" style="background:#0c4a6e;">Alle</button>
      <div id="groupFilterBtns" class="flex gap-2 flex-wrap"></div>
      <div class="ml-auto relative">
        <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">search</span>
        <input id="empSearchInput" type="text" placeholder="Mitarbeiter…" oninput="renderPlanningTable()" class="pl-9 pr-3 py-1.5 rounded-full border border-slate-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm w-36 transition-all focus:w-56 text-slate-700 placeholder-slate-400 bg-white shadow-sm">
      </div>
    </div>
    <div id="planningLoading" class="text-center py-16 text-slate-400">Lade Daten...</div>
    <div id="planningTable" class="table-scroll hidden"></div>
  </div>

</div>

<!-- Tab: Groups -->
<div id="panel-groups" class="p-4 pt-8 bg-white rounded-xl border border-slate-200 mt-8 hidden">
  <!-- Toolbar -->
  <div class="flex items-center justify-between mb-4 flex-wrap gap-2">
    <div>
      <h2 class="text-lg font-semibold text-slate-800">Gruppen &amp; Mitarbeiterzuordnung</h2>
      <p class="text-xs text-slate-500 mt-0.5">Mitarbeiter per Drag &amp; Drop zwischen Gruppen verschieben. Die Spalte <span class="font-medium text-amber-600">Ohne Gruppe</span> zeigt nicht zugeordnete Mitarbeiter.</p>
    </div>
    <div class="flex gap-2">
      <button onclick="showAddGroupModal()" class="px-4 py-2 text-white rounded-lg text-sm flex items-center gap-1 hover:opacity-90" style="background:#0c4a6e;">
        <span class="material-icons-outlined text-base">add</span> Neue Gruppe
      </button>
    </div>
  </div>

  <!-- Kanban Board -->
  <div id="kanbanBoard" class="flex gap-3 overflow-x-auto pb-4 min-h-[400px] items-start"></div>
</div>

<!-- Tab: Quotas -->
<div id="panel-quotas" class="p-6 pt-8 bg-white rounded-xl border border-slate-200 mt-8 hidden">
  <div class="max-w-2xl">
    <div class="flex items-center justify-between mb-4">
      <h2 class="text-lg font-semibold text-slate-800">Urlaubsquoten</h2>
      <div class="flex items-center gap-2">
        <label class="text-sm text-slate-600">Jahr:</label>
        <select id="quotaYearSelect" onchange="loadQuotas()" class="border border-slate-300 rounded-lg px-3 py-1.5 text-sm bg-white"></select>
      </div>
    </div>
    <div id="quotasList" class="space-y-2"></div>
    <button onclick="saveQuotas()" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">Quoten speichern</button>
  </div>
</div>

<!-- Tab: Holidays -->
<div id="panel-holidays" class="p-6 pt-8 bg-white rounded-xl border border-slate-200 mt-8 hidden">

  <!-- Toolbar -->
  <div class="flex flex-wrap items-center gap-3 mb-6">
    <div class="flex items-center gap-2">
      <label class="text-sm text-slate-600 font-medium">Jahr:</label>
      <select id="holidayYearSelect" onchange="loadHolidays()" class="border border-slate-300 rounded-lg px-3 py-1.5 text-sm bg-white"></select>
    </div>
  </div>

  <!-- Kanban Board -->
  <div class="grid grid-cols-2 gap-5" style="align-items:start;">

    <!-- Spalte: Feiertage -->
    <div class="bg-slate-50 rounded-2xl border border-slate-200 flex flex-col">
      <div class="flex items-center gap-2 px-4 py-3 border-b border-slate-200">
        <span class="w-2.5 h-2.5 rounded-full bg-red-400 flex-shrink-0"></span>
        <h2 class="text-sm font-semibold text-slate-700">Brandenburger Feiertage</h2>
        <span id="holidaysCount" class="ml-auto text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-600 font-medium">0</span>
      </div>
      <p class="text-xs text-slate-400 px-4 pt-2 pb-1">Gesetzlich — automatisch berechnet, nicht editierbar.</p>
      <div id="holidaysList" class="p-3 space-y-2 overflow-y-auto" style="max-height:70vh;"></div>
    </div>

    <!-- Spalte: Schließtage -->
    <div class="bg-slate-50 rounded-2xl border border-slate-200 flex flex-col">
      <div class="flex items-center gap-2 px-4 py-3 border-b border-slate-200">
        <span class="w-2.5 h-2.5 rounded-full bg-orange-400 flex-shrink-0"></span>
        <h2 class="text-sm font-semibold text-slate-700">Firmeninterne Schließtage</h2>
        <span id="closuresCount" class="ml-auto text-xs px-2 py-0.5 rounded-full bg-orange-100 text-orange-600 font-medium">0</span>
        {% if 'ADMIN' in session.get('roles', []) %}
        <button onclick="document.getElementById('closureFormInline').classList.toggle('hidden')" class="ml-1 px-3 py-1 bg-sky-900 text-white rounded-lg text-xs hover:bg-sky-800 font-medium flex-shrink-0">+ Schließtag</button>
        {% endif %}
      </div>
      {% if 'ADMIN' in session.get('roles', []) %}
      <div id="closureFormInline" class="hidden px-4 py-3 border-b border-slate-200 bg-white flex flex-wrap items-end gap-2">
        <div>
          <label class="text-xs text-slate-500 block mb-1">Datum</label>
          <input type="date" id="closureDateInput" class="border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white">
        </div>
        <div>
          <label class="text-xs text-slate-500 block mb-1">Bezeichnung</label>
          <input type="text" id="closureNameInput" placeholder="z.B. Betriebsferien" class="border border-slate-300 rounded-lg px-3 py-2 text-sm w-48">
        </div>
        <button onclick="addClosure()" class="px-3 py-2 bg-sky-900 text-white rounded-lg text-sm hover:bg-sky-800 font-medium">Eintragen</button>
        <button onclick="document.getElementById('closureFormInline').classList.add('hidden')" class="px-3 py-2 bg-slate-100 text-slate-600 rounded-lg text-sm hover:bg-slate-200">Abbrechen</button>
      </div>
      {% endif %}
      <p class="text-xs text-slate-400 px-4 pt-2 pb-1">Betriebsferien, Brückentage etc. — erscheinen im Planungsraster wie Feiertage.</p>
      <div id="closuresList" class="p-3 space-y-2 overflow-y-auto" style="max-height:70vh;">
        <p class="text-slate-400 text-sm px-1">Lade...</p>
      </div>
    </div>

  </div>
</div><!-- /panel-holidays -->

<!-- Toast -->
<div id="toast" class="hidden">
  <div id="toastMsg" class="bg-slate-800 text-white px-5 py-3 rounded-xl shadow-xl text-sm font-medium"></div>
</div>

<!-- Add Group Modal -->
<div id="groupModal" class="fixed inset-0 bg-black/40 z-50 hidden" style="display:none;align-items:center;justify-content:center;">
  <div class="bg-white rounded-2xl shadow-2xl p-6 w-96 max-w-full" style="margin:auto;margin-top:10vh;">
    <h3 class="text-lg font-semibold text-slate-800 mb-4" id="groupModalTitle">Neue Gruppe</h3>
    <div class="space-y-3">
      <div>
        <label class="text-xs text-slate-500 block mb-1">Kürzel (z.B. M, FE, ...)</label>
        <input id="groupIdInput" type="text" class="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm" placeholder="M">
      </div>
      <div>
        <label class="text-xs text-slate-500 block mb-1">Name</label>
        <input id="groupNameInput" type="text" class="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm" placeholder="Mechanik">
      </div>
      <div>
        <label class="text-xs text-slate-500 block mb-1">Farbe</label>
        <input id="groupColorInput" type="color" value="#3b82f6" class="w-full h-10 border border-slate-300 rounded-lg cursor-pointer">
      </div>
    </div>
    <div class="flex gap-3 mt-5">
      <button onclick="saveGroup()" class="flex-1 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 font-medium">Speichern</button>
      <button onclick="closeGroupModal()" class="flex-1 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm hover:bg-slate-200">Abbrechen</button>
    </div>
  </div>
</div>

<!-- Password Modal (required by sidebar) -->
<div id="passwordModal" class="fixed inset-0 bg-black/40 z-50" style="display:none;align-items:center;justify-content:center;">
  <div class="bg-white rounded-2xl shadow-2xl p-6 w-96 max-w-full" style="margin:auto;margin-top:10vh;">
    <h3 class="text-lg font-semibold text-slate-800 mb-4">Passwort ändern</h3>
    <div class="space-y-3">
      <input id="pwOld" type="password" placeholder="Altes Passwort" class="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm">
      <input id="pwNew" type="password" placeholder="Neues Passwort" class="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm">
      <input id="pwNew2" type="password" placeholder="Neues Passwort bestätigen" class="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm">
    </div>
    <div id="pwMsg" class="text-sm mt-2 hidden"></div>
    <div class="flex gap-3 mt-5">
      <button onclick="submitPasswordChange()" class="flex-1 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 font-medium">Ändern</button>
      <button onclick="document.getElementById('passwordModal').style.display='none'" class="flex-1 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm hover:bg-slate-200">Abbrechen</button>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
const STATUS_CYCLE = ['', 'U', 'K', 'TZ', 'G', 'Xs', 'D'];
const SHIFT_CYCLE = ['', 'F', 'S', 'N'];
const canEdit = {{ can_edit | tojson }};

let weekOffset = 0;
let numWeeks = 4;

function setNumWeeks(n) {
  numWeeks = n;
  document.querySelectorAll('.weeks-btn').forEach(function(btn) {
    const active = parseInt(btn.dataset.weeks) === n;
    btn.className = 'weeks-btn px-3 py-1.5 rounded-full text-sm font-medium ' +
      (active ? 'text-white' : 'bg-slate-100 text-slate-700 hover:bg-slate-200');
    if (active) btn.style.background = '#0c4a6e'; else btn.style.background = '';
  });
  const lbl = document.getElementById('weeksTriggerLabel');
  if (lbl) lbl.textContent = n + 'W';
  loadData();
}

(function() {
  function initWeeksFlyout() {
    const wrap = document.querySelector('.weeks-flyout-wrap');
    const flyout = document.getElementById('weeksFlyout');
    const chevron = document.getElementById('weeksChevron');
    if (!wrap || !flyout) return;
    // measure natural width once
    flyout.style.width = 'auto';
    flyout.style.opacity = '0';
    const fullWidth = flyout.scrollWidth;
    flyout.style.width = '0';

    let hideTimer;
    function show() {
      clearTimeout(hideTimer);
      flyout.style.width = fullWidth + 'px';
      flyout.style.opacity = '1';
      chevron.style.transform = 'rotate(180deg)';
    }
    function hide() {
      hideTimer = setTimeout(function() {
        flyout.style.width = '0';
        flyout.style.opacity = '0';
        chevron.style.transform = 'rotate(0deg)';
      }, 120);
    }
    wrap.addEventListener('mouseenter', show);
    wrap.addEventListener('mouseleave', hide);
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initWeeksFlyout);
  } else {
    initWeeksFlyout();
  }
})();
let planningData = null;
let groupsData = [];
let quotasData = {};
let pendingChanges = {};
let isTableLocked = true;
let activeGroupFilter = null;
let editingGroupId = null;
const todayStr = new Date().toISOString().split('T')[0];

// Legende-Popover schließen wenn außerhalb geklickt
document.addEventListener('click', function(e) {
  const pop = document.getElementById('legendePopover');
  const wrap = document.getElementById('legendeWrap');
  if (pop && wrap && !wrap.contains(e.target)) pop.classList.add('hidden');
});

function showToast(msg, isError) {
  const t = document.getElementById('toast');
  const m = document.getElementById('toastMsg');
  m.textContent = msg;
  m.className = 'px-5 py-3 rounded-xl shadow-xl text-sm font-medium ' + (isError ? 'bg-red-600' : 'bg-slate-800') + ' text-white';
  t.classList.remove('hidden');
  setTimeout(function() { t.classList.add('hidden'); }, 3000);
}

function switchTab(tab) {
  ['planning','groups','quotas','holidays'].forEach(function(t) {
    document.getElementById('panel-'+t).classList.add('hidden');
    document.getElementById('tab-'+t).className = 'tab-btn whitespace-nowrap border-b-2 px-4 pb-3 pt-2 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700';
  });
  document.getElementById('panel-'+tab).classList.remove('hidden');
  document.getElementById('tab-'+tab).className = 'tab-btn whitespace-nowrap border-b-2 px-4 pb-3 pt-2 text-sm font-medium border-primary text-primary';
  const fab = document.getElementById('saveFab');
  if (fab) fab.style.display = tab === 'planning' ? 'flex' : 'none';
  if (tab === 'groups') loadGroupsTab();
  if (tab === 'quotas') loadQuotas();
  if (tab === 'holidays') loadHolidays();
}

function changeWeek(delta) { weekOffset += delta; loadData(); }
function jumpToToday() { weekOffset = 0; loadData(); }

async function loadData(silent) {
  if (!silent) {
    document.getElementById('planningLoading').classList.remove('hidden');
    document.getElementById('planningTable').classList.add('hidden');
  }
  try {
    const r = await fetch('/api/attendance/data?week_offset=' + weekOffset + '&num_weeks=' + numWeeks);
    planningData = await r.json();
    groupsData = planningData.groups || [];
    renderGroupFilterBtns();
    renderPlanningTable();
  } catch(e) { showToast('Fehler beim Laden', true); }
  if (!silent) {
    document.getElementById('planningLoading').classList.add('hidden');
    document.getElementById('planningTable').classList.remove('hidden');
  }
}

function renderGroupFilterBtns() {
  const c = document.getElementById('groupFilterBtns');
  c.innerHTML = '';
  groupsData.forEach(function(g) {
    const btn = document.createElement('button');
    btn.id = 'filter-' + g.id;
    btn.textContent = g.id + (g.name !== g.id ? ' (' + g.name + ')' : '');
    btn.className = 'px-3 py-1 rounded-full text-xs font-medium border border-slate-300 text-slate-600 hover:bg-slate-100';
    btn.style.borderColor = g.color;
    btn.onclick = function() { setGroupFilter(g.id); };
    c.appendChild(btn);
  });
}

function setGroupFilter(gid) {
  activeGroupFilter = gid;
  const filterAllBtn = document.getElementById('filter-all');
  filterAllBtn.className = 'px-3 py-1 rounded-full text-xs font-medium ' + (gid === null ? 'text-white' : 'bg-slate-100 text-slate-600 hover:bg-slate-200');
  filterAllBtn.style.background = gid === null ? '#0c4a6e' : '';
  groupsData.forEach(function(g) {
    const btn = document.getElementById('filter-' + g.id);
    if(!btn) return;
    if(g.id === gid) {
      btn.className = 'px-3 py-1 rounded-full text-xs font-medium text-white';
      btn.style.backgroundColor = g.color;
    } else {
      btn.className = 'px-3 py-1 rounded-full text-xs font-medium border border-slate-300 text-slate-600 hover:bg-slate-100';
      btn.style.backgroundColor = '';
    }
  });
  renderPlanningTable();
}

function getGroupColor(gid) {
  const g = groupsData.find(function(g) { return g.id === gid; });
  return g ? g.color : '#6b7280';
}

function renderPlanningTable() {
  const container = document.getElementById('planningTable');
  if (!planningData || !planningData.weeks) {
    container.innerHTML = '<p class="text-slate-400 py-8 text-center">Keine Daten.</p>';
    return;
  }
  const weeks = planningData.weeks;
  const searchRaw = (document.getElementById('empSearchInput') || {}).value || '';
  const searchTerm = searchRaw.trim().toLowerCase();
  const clearBtn = document.getElementById('empSearchClear');
  if (clearBtn) clearBtn.classList.toggle('hidden', !searchTerm);
  const employees = (planningData.employees || []).filter(function(e) {
    if (activeGroupFilter && e.group_id !== activeGroupFilter) return false;
    if (searchTerm && !(e.name || '').toLowerCase().includes(searchTerm)) return false;
    return true;
  });

  if(weeks.length > 0) {
    const first = weeks[0], last = weeks[weeks.length-1];
    document.getElementById('weekRangeLabel').textContent =
      'KW ' + first.kw_num + ' - KW ' + last.kw_num + ' (' + formatDate(first.days[0].date) + ' - ' + formatDate(last.days[last.days.length-1].date) + ')';
  }

  // Compute staffing per day per group (nur Werktage)
  const staffingMap = {};
  (planningData.employees || []).forEach(function(emp) {
    weeks.forEach(function(week) {
      week.days.forEach(function(day) {
        if(day.is_weekend) return; // Wochenenden nicht in Mindestbesetzung
        if(!staffingMap[day.date]) staffingMap[day.date] = {};
        if(!staffingMap[day.date][emp.group_id]) staffingMap[day.date][emp.group_id] = {present:0,total:0};
        const g = staffingMap[day.date][emp.group_id];
        g.total++;
        const sched = (emp.schedule[week.week_key] || {});
        const dayData = ((sched.days || {})[day.date] || {});
        const pc = pendingChanges[emp.id];
        const evt = (pc && pc[week.week_key] && pc[week.week_key].days && pc[week.week_key].days[day.date] !== undefined)
          ? (pc[week.week_key].days[day.date] === null ? null : pc[week.week_key].days[day.date].ereignis)
          : (dayData.ereignis || '');
        if(evt !== null && !evt && !day.holiday) g.present++;
      });
    });
  });

  let html = '<table class="border-collapse text-xs w-full" style="min-width:600px"><thead>';
  // Row 1: week headers
  html += '<tr><th class="sticky-col border border-slate-200 px-3 py-2 text-left font-medium text-slate-600 bg-white" style="min-width:180px">Mitarbeiter</th>';
  html += '<th class="sticky-col2 border border-slate-200 px-2 py-2 text-center font-medium text-slate-600 bg-white" style="min-width:60px">Gruppe</th>';
  weeks.forEach(function(week) {
    const cs = week.days.length + 1;
    html += '<th colspan="' + cs + '" class="border border-slate-200 px-2 py-2 text-center font-semibold text-white" style="background:#0c4a6e;">KW ' + week.kw_num + ' &middot; ' + week.year + '</th>';
  });
  if(canEdit) html += '<th class="border border-slate-200 px-2 py-2 text-center text-slate-500 bg-white">Urlaub</th>';
  html += '</tr>';

  // Row 2: day headers
  html += '<tr class="bg-slate-50"><th class="sticky-col bg-slate-50 border border-slate-200"></th><th class="sticky-col2 bg-slate-50 border border-slate-200"></th>';
  weeks.forEach(function(week) {
    html += '<th class="border border-slate-200 px-1 py-1 text-center text-slate-500 font-medium">Schicht</th>';
    week.days.forEach(function(day) {
      const isToday = day.date === todayStr;
      const isHol = day.holiday;
      const isWE = day.is_weekend;
      const bg = isToday ? 'bg-yellow-50' : isHol ? 'bg-red-50' : isWE ? 'bg-slate-100' : 'bg-slate-50';
      const titleAttr = isHol ? ' title="' + escapeHtml(day.holiday_name || 'Feiertag') + '"' : '';
      const textColor = isToday ? 'text-blue-700' : isHol ? 'text-red-400' : isWE ? 'text-slate-400' : 'text-slate-600';
      const subColor = isHol ? 'text-red-300' : isWE ? 'text-slate-300' : 'text-slate-400';
      html += '<th class="border border-slate-200 px-1 py-1 text-center ' + bg + '"' + titleAttr + '>';
      html += '<div class="font-medium ' + textColor + '">' + day.dow + '</div>';
      html += '<div class="' + subColor + '">' + day.date.slice(5) + '</div></th>';
    });
  });
  html += '<th class="bg-slate-50 border border-slate-200 px-2 py-1 text-center text-slate-500">Rest</th></tr></thead><tbody>';

  // Group employees
  const grouped = {};
  employees.forEach(function(emp) {
    const gid = emp.group_id || '?';
    if(!grouped[gid]) grouped[gid] = [];
    grouped[gid].push(emp);
  });

  Object.keys(grouped).forEach(function(gid) {
    const emps = grouped[gid];
    const gc = getGroupColor(gid);
    const gname = (groupsData.find(function(g) { return g.id === gid; }) || {}).name || gid;
    const totalCols = 2 + weeks.reduce(function(a,w) { return a + w.days.length + 1; }, 0) + 1;
    html += '<tr><td colspan="' + totalCols + '" class="py-1 px-3 text-xs font-semibold" style="background:' + gc + '22;color:' + gc + ';border-bottom:2px solid ' + gc + '44">' + escapeHtml(gid) + ' &mdash; ' + escapeHtml(gname) + '</td></tr>';

    emps.forEach(function(emp) {
      const vacUsed = countVacation(emp, weeks);
      const quota = quotasData[emp.id] ? (quotasData[emp.id][String(new Date().getFullYear())] || 30) : 30;
      const vacLeft = quota - vacUsed;
      const lowVac = vacLeft < 5;
      html += '<tr class="hover:bg-slate-50">';
      const sm = emp.shift_model || 'none';
      const libModels = (planningData.shift_model_library || []);
      const libEntry = libModels.find(function(m) { return m.id === sm; });
      const smDisplayColor = libEntry ? 'bg-purple-100 text-purple-700' : 'bg-gray-100 text-gray-500';
      const refWkDisplay = (emp.shift_model_ref_week && sm !== 'none') ? fmtRefWeek(emp.shift_model_ref_week) : '';
      const libOptions = libModels.map(function(m) { return '<option value="' + m.id + '"' + (m.id === sm ? ' selected' : '') + '>' + escapeHtml(m.name) + '</option>'; }).join('');
      html += '<td class="sticky-col border border-slate-200 px-2 py-1 bg-white">' +
        '<div class="font-medium text-slate-700 text-xs whitespace-nowrap">' + escapeHtml(emp.name) + '</div>' +
        (canEdit ? (
          '<div class="flex items-center gap-0.5 mt-0.5">' +
            '<select data-action="set-shift-model" data-emp-id="' + emp.id + '" class="text-xs rounded px-1 py-0.5 border-0 font-medium ' + smDisplayColor + (isTableLocked ? ' cursor-not-allowed opacity-60' : ' cursor-pointer') + '" title="Schichtmodell"' + (isTableLocked ? ' disabled' : '') + '>' +
              '<option value="none"' + (sm === 'none' ? ' selected' : '') + '>–</option>' +
              libOptions +
            '</select>' +
            '<button data-action="apply-shift-model" data-emp-id="' + emp.id + '" class="transition-colors px-0.5 ' + (isTableLocked ? 'text-slate-300 cursor-not-allowed' : 'text-slate-400 hover:text-blue-600') + '" title="Referenz setzen und auf alle sichtbaren Wochen anwenden"' + (isTableLocked ? ' disabled' : '') + '>&#9654;</button>' +
          '</div>' +
          (refWkDisplay ? '<div class="text-slate-400 mt-0.5" style="font-size:9px">' + (libEntry ? escapeHtml(libEntry.name) + ' · ' : '') + refWkDisplay + '</div>' : '')
        ) : (
          sm !== 'none' ? '<div class="text-xs text-slate-400 mt-0.5">' + escapeHtml(libEntry ? libEntry.name : sm) + '</div>' : ''
        )) +
      '</td>';
      html += '<td class="sticky-col2 border border-slate-200 px-2 py-1 text-center bg-white"><span class="group-badge text-white" style="background:' + gc + '">' + escapeHtml(gid) + '</span></td>';

      weeks.forEach(function(week) {
        const wkey = week.week_key;
        const schedWeek = (emp.schedule[wkey] || {});
        const pc = pendingChanges[emp.id];
        const pendShift = (pc && pc[wkey] && pc[wkey].shift !== undefined) ? pc[wkey].shift : undefined;
        const shift = pendShift !== undefined ? pendShift : (schedWeek.shift || '');
        const isProjected = pendShift === undefined && schedWeek.projected === true;
        const shiftTints = { F: 'rgba(219,234,254,0.4)', S: 'rgba(254,243,199,0.5)', N: 'rgba(187,247,208,0.35)' };
        const shiftTint = shiftTints[shift] || '';
        const shiftTintStyle = shiftTint ? ' style="background:' + shiftTint + '"' : '';
        const shiftClick = (canEdit && !isTableLocked) ? ' onclick="cycleShift(\\'' + emp.id + '\\',\\'' + wkey + '\\',this)"' : '';
        const shiftTitle = canEdit && isProjected ? ' title="Vorausberechnet (' + (emp.shift_model||'') + ') – klicken zum Bestätigen"' : '';
        html += '<td class="border border-slate-200 px-1 py-1 text-center"' + shiftTintStyle + '><span class="shift-badge shift-' + (shift||'empty') + (isProjected ? ' shift-projected' : '') + '"' + shiftTitle + shiftClick + '>' + (shift||'&mdash;') + '</span></td>';

        week.days.forEach(function(day) {
          const isHol = day.holiday;
          const isWE = day.is_weekend;
          const dayData = ((schedWeek.days || {})[day.date] || {});
          const pcEntry = (pc && pc[wkey] && pc[wkey].days) ? pc[wkey].days[day.date] : undefined;
          const evt = pcEntry !== undefined
            ? (pcEntry === null ? null : pcEntry.ereignis)
            : (Object.prototype.hasOwnProperty.call(dayData, 'ereignis') ? dayData.ereignis : (isWE ? null : ''));
          const isToday = day.date === todayStr;
          const todayBg = isToday ? 'today-col' : '';
          const weBg = isWE ? 'bg-slate-100' : '';

          if(isHol) {
            html += '<td class="border border-slate-200 p-0.5 bg-red-50 ' + todayBg + '" title="' + escapeHtml(day.holiday_name || 'Feiertag') + '">';
            html += '<div class="status-cell holiday">F</div></td>';
          } else if(isWE) {
            const working = evt !== null && evt !== undefined;
            html += '<td class="border border-slate-200 p-0.5 ' + weBg + ' ' + todayBg + '">';
            if(canEdit) {
              if(working) {
                html += '<div class="status-cell status-we-work' + (isTableLocked ? ' readonly' : '') + '"' + (!isTableLocked ? ' onclick="toggleWeekend(\\'' + emp.id + '\\',\\'' + wkey + '\\',\\'' + day.date + '\\',this)"' : '') + ' title="Sondereinsatz' + (!isTableLocked ? ' — klicken zum Entfernen' : '') + '">&#x2731;</div>';
              } else {
                html += '<div class="status-cell status-we-off' + (isTableLocked ? ' readonly' : '') + '"' + (!isTableLocked ? ' onclick="toggleWeekend(\\'' + emp.id + '\\',\\'' + wkey + '\\',\\'' + day.date + '\\',this)"' : '') + ' title="Frei' + (!isTableLocked ? ' — klicken für Sondereinsatz' : '') + '">&mdash;</div>';
              }
            } else {
              html += '<div class="status-cell ' + (working ? 'status-we-work' : 'status-we-off') + '">' + (working ? '&#x2731;' : '&mdash;') + '</div>';
            }
            html += '</td>';
          } else {
            const st = (staffingMap[day.date] || {})[gid] || {present:0,total:0};
            const warnDay = st.total > 0 && st.present < 2;
            const warnBg = warnDay && !evt ? 'bg-amber-50' : '';
            const cellTint = shiftTint && !warnBg && !isToday ? ' style="background:' + shiftTint + '"' : '';
            html += '<td class="border border-slate-200 p-0.5 ' + warnBg + ' ' + todayBg + '"' + cellTint + '>';
            const clickAttr = (canEdit && !isTableLocked) ? ' onclick="cycleStatus(\\'' + emp.id + '\\',\\'' + wkey + '\\',\\'' + day.date + '\\',this)"' : '';
            const isRedacted = evt === '●';
            const statusClass = isRedacted ? 'status-redacted' : 'status-' + (evt||'empty');
            const roClass = (!canEdit || isTableLocked) ? ' readonly' : '';
            html += '<div class="status-cell ' + statusClass + roClass + '"' + clickAttr + '>' + escapeHtml(evt||'') + '</div>';
            html += '</td>';
          }
        });
      });

      if(canEdit) html += '<td class="border border-slate-200 px-2 py-1 text-center ' + (lowVac ? 'text-red-600 font-bold' : 'text-slate-600') + '">' + vacLeft + '</td>';
      html += '</tr>';
    });

    // Staffing count row
    html += '<tr class="bg-slate-50 text-slate-400"><td class="sticky-col bg-slate-50 border border-slate-200 px-3 py-1 text-xs italic">Anwesend ' + escapeHtml(gname) + '</td><td class="sticky-col2 bg-slate-50 border border-slate-200"></td>';
    weeks.forEach(function(week) {
      html += '<td class="border border-slate-200"></td>';
      week.days.forEach(function(day) {
        if(day.holiday) { html += '<td class="border border-slate-200 px-1 py-1 text-center bg-red-50 text-red-300">F</td>'; return; }
        if(day.is_weekend) { html += '<td class="border border-slate-200 px-1 py-1 text-center bg-slate-100 text-slate-300">&mdash;</td>'; return; }
        const st = (staffingMap[day.date] || {})[gid] || {present:0,total:0};
        const warn = st.total > 0 && st.present < 2;
        html += '<td class="border border-slate-200 px-1 py-1 text-center ' + (warn ? 'text-amber-600 font-bold bg-amber-50' : '') + '">' + st.present + '/' + st.total + '</td>';
      });
    });
    html += '<td class="border border-slate-200"></td></tr>';
  });

  html += '</tbody></table>';
  container.innerHTML = html;
  attachPlanningTableListeners();
}

function attachPlanningTableListeners() {
  const container = document.getElementById('planningTable');
  if (container._shiftModelListenerAttached) return;
  container._shiftModelListenerAttached = true;

  container.addEventListener('change', function(evt) {
    const sel = evt.target.closest('select[data-action="set-shift-model"]');
    if (!sel) return;
    if (isTableLocked) return;
    const empId = sel.dataset.empId;
    const model = sel.value;
    const isLib = (planningData.shift_model_library || []).some(function(m) { return m.id === model; });
    sel.className = sel.className.replace(/bg-\S+|text-\S+/g, '').trim();
    const colorClasses = isLib ? ['bg-purple-100','text-purple-700'] : ['bg-gray-100','text-gray-500'];
    colorClasses.forEach(function(c) { sel.classList.add(c); });
    fetch('/api/attendance/employees/' + encodeURIComponent(empId) + '/shift_model', {
      method: 'PUT',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({shift_model: model})
    }).then(function(r) { return r.json(); }).then(function(data) {
      if (data.ok) {
        showToast('Schichtmodell gespeichert');
        loadData(true);
      } else {
        showToast(data.error || 'Fehler beim Speichern', true);
      }
    }).catch(function() { showToast('Netzwerkfehler', true); });
  });

  container.addEventListener('click', function(evt) {
    const btn = evt.target.closest('button[data-action="apply-shift-model"]');
    if (!btn) return;
    if (isTableLocked) return;
    const empId = btn.dataset.empId;
    const sel = btn.parentElement.querySelector('select[data-action="set-shift-model"]');
    if (!sel || !planningData || !planningData.weeks) return;
    const model = sel.value;
    const weeks = planningData.weeks;
    if (!weeks.length) return;

    // Reference = first visible week
    const refWeek = weeks[0].week_key;
    let refShift = '';
    let libPattern = null;
    const libEntry2 = (planningData.shift_model_library || []).find(function(m) { return m.id === model; });
    if (libEntry2) {
      libPattern = libEntry2.pattern;
    }

    fetch('/api/attendance/employees/' + encodeURIComponent(empId) + '/shift_model', {
      method: 'PUT',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({shift_model: model, ref_week: refWeek, ref_shift: refShift})
    }).then(function(r) { return r.json(); }).then(function(data) {
      if (!data.ok) { showToast(data.error || 'Fehler beim Speichern', true); return; }
      if (!pendingChanges[empId]) pendingChanges[empId] = {};
      weeks.forEach(function(week, idx) {
        if (!pendingChanges[empId][week.week_key]) pendingChanges[empId][week.week_key] = {days:{}};
        let shift = '';
        if (libPattern) shift = libPattern[idx % libPattern.length] || '';
        pendingChanges[empId][week.week_key].shift = shift;
      });
      showToast('Referenz gesetzt ab ' + fmtRefWeek(refWeek) + ' — bitte speichern');
      loadData(true);
    }).catch(function() { showToast('Netzwerkfehler', true); });
  });

}

function fmtRefWeek(wkey) {
  if (!wkey) return '';
  const m = wkey.match(/(\d{4})-W(\d+)/);
  if (!m) return wkey;
  return 'ab KW ' + parseInt(m[2]) + '/' + m[1].slice(2);
}

function countVacation(emp, weeks) {
  let count = 0;
  const year = String(new Date().getFullYear());
  Object.keys(emp.schedule).forEach(function(wkey) {
    if(!wkey.startsWith(year)) return;
    const wdata = emp.schedule[wkey];
    Object.values(wdata.days || {}).forEach(function(d) {
      if((d.ereignis || '') === 'U') count++;
    });
  });
  return count;
}

function cycleStatus(empId, weekKey, dateStr, el) {
  if (isTableLocked) return;
  const current = el.textContent.trim() || '';
  const idx = STATUS_CYCLE.indexOf(current);
  const next = STATUS_CYCLE[(idx+1) % STATUS_CYCLE.length];
  el.textContent = next;
  el.className = 'status-cell status-' + (next||'empty');
  if(!pendingChanges[empId]) pendingChanges[empId] = {};
  if(!pendingChanges[empId][weekKey]) pendingChanges[empId][weekKey] = {days:{}};
  if(!pendingChanges[empId][weekKey].days) pendingChanges[empId][weekKey].days = {};
  pendingChanges[empId][weekKey].days[dateStr] = {ereignis: next};
  updateFabState();
  renderPlanningTable();
}

function cycleShift(empId, weekKey, el) {
  if (isTableLocked) return;
  const current = el.textContent.trim();
  const mapped = current === '&mdash;' || current === '—' ? '' : current;
  const idx = SHIFT_CYCLE.indexOf(mapped);
  const next = SHIFT_CYCLE[(idx+1) % SHIFT_CYCLE.length];
  el.textContent = next || '—';
  el.className = 'shift-badge shift-' + (next||'empty');
  if(!pendingChanges[empId]) pendingChanges[empId] = {};
  if(!pendingChanges[empId][weekKey]) pendingChanges[empId][weekKey] = {days:{}};
  pendingChanges[empId][weekKey].shift = next;
  updateFabState();
}

function toggleWeekend(empId, weekKey, dateStr, el) {
  if (isTableLocked) return;
  const working = el.classList.contains('status-we-work');
  if(!pendingChanges[empId]) pendingChanges[empId] = {};
  if(!pendingChanges[empId][weekKey]) pendingChanges[empId][weekKey] = {days:{}};
  if(!pendingChanges[empId][weekKey].days) pendingChanges[empId][weekKey].days = {};
  if(working) {
    // null = Eintrag löschen (zurück zu "frei")
    pendingChanges[empId][weekKey].days[dateStr] = null;
    el.className = 'status-cell status-we-off';
    el.innerHTML = '&mdash;';
    el.title = 'Frei — klicken für Sondereinsatz';
  } else {
    pendingChanges[empId][weekKey].days[dateStr] = {ereignis: ''};
    el.className = 'status-cell status-we-work';
    el.innerHTML = '&#x2731;';
    el.title = 'Sondereinsatz — klicken zum Entfernen';
  }
  updateFabState();
}

async function uploadCsv(input) {
  const file = input.files[0];
  if(!file) return;
  if(!file.name.toLowerCase().endsWith('.csv')) { showToast('Nur CSV-Dateien erlaubt.', true); input.value=''; return; }
  const label = input.closest('label');
  const origText = label.querySelector('span:last-of-type') ? label.querySelector('span:last-of-type').textContent : 'CSV Import';
  label.style.opacity = '0.6';
  label.style.pointerEvents = 'none';
  showToast('CSV wird importiert...');
  const fd = new FormData();
  fd.append('presence_file', file);
  try {
    const r = await fetch('/api/upload_employee_presence', {method: 'POST', body: fd});
    const d = await r.json();
    if(r.ok) {
      showToast(d.message || 'CSV erfolgreich importiert.');
      await loadData();
    } else {
      showToast(d.error || 'Fehler beim Import.', true);
    }
  } catch(e) {
    showToast('Netzwerkfehler beim CSV-Import.', true);
  }
  input.value = '';
  label.style.opacity = '';
  label.style.pointerEvents = '';
}

function updateFabState() {
  const fab = document.getElementById('saveFab');
  if (!fab) return;
  fab.disabled = false;
  const icon = fab.querySelector('.fab-icon');
  const label = fab.querySelector('.fab-label');
  if (isTableLocked) {
    fab.classList.add('fab-locked');
    fab.style.background = '';
    if (icon) icon.textContent = 'lock_open';
    if (label) label.textContent = 'Bearbeiten';
  } else {
    fab.classList.remove('fab-locked');
    const dirty = Object.keys(pendingChanges).length > 0;
    fab.style.background = dirty ? '#f97316' : '#0c4a6e';
    if (icon) icon.textContent = 'save';
    if (label) label.textContent = 'Speichern';
  }
}
function updateSaveBtns(label, disabled) {
  const fab = document.getElementById('saveFab');
  if (fab) { fab.disabled = disabled; fab.querySelector('.fab-label').textContent = label; }
}
function handleFabClick() {
  if (isTableLocked) {
    isTableLocked = false;
    renderPlanningTable();
    updateFabState();
  } else {
    saveAllChanges();
  }
}
async function saveAllChanges() {
  if(Object.keys(pendingChanges).length === 0) {
    isTableLocked = true;
    renderPlanningTable();
    updateFabState();
    return;
  }
  updateSaveBtns('Speichert…', true);
  try {
    const r = await fetch('/api/attendance/update', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({changes: pendingChanges})
    });
    const data = await r.json();
    if(r.ok) {
      pendingChanges = {};
      showToast('Gespeichert!');
      isTableLocked = true;
      await loadData();
      updateFabState();
      return;
    } else {
      showToast(data.error || 'Fehler beim Speichern', true);
    }
  } catch(e) { showToast('Netzwerkfehler', true); }
  updateSaveBtns('Speichern', false);
  updateFabState();
}

// --- Groups Tab ---
// ── Kanban Board ─────────────────────────────────────────────
let kanbanEmployees = [];

async function loadGroupsTab() {
  const [grpRes, empRes] = await Promise.all([
    fetch('/api/attendance/groups'),
    fetch('/api/attendance/employees')
  ]);
  groupsData = await grpRes.json();
  kanbanEmployees = await empRes.json();
  renderKanbanBoard();
  attachKanbanBoardListeners();
}

function attachKanbanBoardListeners() {
  const board = document.getElementById('kanbanBoard');
  board.onclick = function(evt) {
    const btn = evt.target.closest('[data-action]');
    if (!btn) return;
    const action = btn.dataset.action;
    if (action === 'edit-group')   editGroup(btn.dataset.gid);
    if (action === 'delete-group') deleteGroup(btn.dataset.gid);
  };
}

function renderKanbanBoard() {
  const board = document.getElementById('kanbanBoard');
  board.innerHTML = '';

  // Spalten: alle Gruppen + "Ohne Gruppe" am Ende
  const cols = [...groupsData, {id: '__none__', name: 'Ohne Gruppe', color: '#f59e0b'}];

  cols.forEach(function(col) {
    const isUnassigned = col.id === '__none__';
    const emps = kanbanEmployees.filter(function(e) {
      return isUnassigned ? !e.group_id || e.group_id === '' : e.group_id === col.id;
    });

    const colEl = document.createElement('div');
    colEl.className = 'flex-shrink-0 w-52 flex flex-col rounded-xl border ' +
      (isUnassigned ? 'border-amber-300 bg-amber-50' : 'border-slate-200 bg-slate-50');
    colEl.dataset.groupId = col.id;

    // Spalten-Header
    const headerBtns = isUnassigned ? '' :
      '<button data-action="edit-group" data-gid="' + col.id + '" class="opacity-0 group-hover:opacity-100 text-slate-400 hover:text-slate-700 transition-opacity" title="Bearbeiten"><span class="material-icons-outlined text-base">edit</span></button>' +
      '<button data-action="delete-group" data-gid="' + col.id + '" class="opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-600 transition-opacity" title="Löschen"><span class="material-icons-outlined text-base">delete_outline</span></button>';

    colEl.innerHTML =
      '<div class="group flex items-center gap-2 px-3 py-2.5 border-b ' + (isUnassigned ? 'border-amber-200' : 'border-slate-200') + '">' +
        '<div class="w-3 h-3 rounded-full flex-shrink-0" style="background:' + col.color + '"></div>' +
        '<div class="flex-1 min-w-0">' +
          '<div class="text-sm font-semibold text-slate-800 truncate">' + escapeHtml(isUnassigned ? 'Ohne Gruppe' : col.id) + '</div>' +
          (isUnassigned ? '' : '<div class="text-xs text-slate-400 truncate">' + escapeHtml(col.name) + '</div>') +
        '</div>' +
        '<span class="text-xs text-slate-400 font-medium">' + emps.length + '</span>' +
        headerBtns +
      '</div>' +
      '<div class="kanban-col-body flex-1 p-2 space-y-1.5 min-h-[120px]" data-group-id="' + col.id + '">' +
        emps.map(function(e) { return renderKanbanCard(e, col.color); }).join('') +
      '</div>';

    board.appendChild(colEl);

    // Sortable auf jede Spalte
    Sortable.create(colEl.querySelector('.kanban-col-body'), {
      group: 'kanban',
      animation: 150,
      ghostClass: 'kanban-ghost',
      dragClass: 'kanban-dragging',
      onEnd: function(evt) {
        const empId = evt.item.dataset.empId;
        const newGroupId = evt.to.dataset.groupId;
        moveEmployeeToGroup(empId, newGroupId === '__none__' ? '' : newGroupId);
      }
    });
  });
}

function renderKanbanCard(emp, groupColor) {
  const initials = emp.initials || (emp.name || '?').split(',').map(function(p) { return p.trim()[0] || ''; }).reverse().join('').toUpperCase().slice(0,2);
  return '<div class="kanban-card flex items-center gap-2 bg-white rounded-lg border border-slate-200 px-2.5 py-2 cursor-grab shadow-sm hover:shadow-md hover:border-slate-300 transition-all" data-emp-id="' + emp.id + '">' +
    '<div class="w-7 h-7 rounded-full flex items-center justify-center text-white text-xs font-bold flex-shrink-0" style="background:' + groupColor + '">' + escapeHtml(initials) + '</div>' +
    '<span class="flex-1 text-xs text-slate-700 font-medium truncate leading-tight">' + escapeHtml(emp.name) + '</span>' +
  '</div>';
}

async function moveEmployeeToGroup(empId, groupId) {
  const emp = kanbanEmployees.find(function(e) { return e.id === empId; });
  if(emp) emp.group_id = groupId;
  const r = await fetch('/api/attendance/employees/' + empId + '/group', {
    method: 'PUT',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({group_id: groupId})
  });
  if(r.ok) {
    showToast(groupId ? 'Gruppe aktualisiert' : 'Aus Gruppe entfernt');
    renderKanbanBoard();
  } else {
    showToast('Fehler beim Speichern', true);
    await loadGroupsTab();
  }
}

function showAddGroupModal() {
  editingGroupId = null;
  document.getElementById('groupModalTitle').textContent = 'Neue Gruppe';
  document.getElementById('groupIdInput').value = '';
  document.getElementById('groupNameInput').value = '';
  document.getElementById('groupColorInput').value = '#3b82f6';
  document.getElementById('groupIdInput').disabled = false;
  document.getElementById('groupModal').style.display = 'flex';
}
function closeGroupModal() { document.getElementById('groupModal').style.display = 'none'; }
function editGroup(gid) {
  const g = groupsData.find(function(g) { return g.id === gid; });
  if(!g) return;
  editingGroupId = gid;
  document.getElementById('groupModalTitle').textContent = 'Gruppe bearbeiten';
  document.getElementById('groupIdInput').value = g.id;
  document.getElementById('groupNameInput').value = g.name;
  document.getElementById('groupColorInput').value = g.color;
  document.getElementById('groupIdInput').disabled = true;
  document.getElementById('groupModal').style.display = 'flex';
}
async function saveGroup() {
  const id = document.getElementById('groupIdInput').value.trim();
  const name = document.getElementById('groupNameInput').value.trim();
  const color = document.getElementById('groupColorInput').value;
  if(!id || !name) { showToast('Kürzel und Name erforderlich', true); return; }
  const r = await fetch('/api/attendance/groups', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({id:id, name:name, color:color, editing:editingGroupId})
  });
  if(r.ok) {
    closeGroupModal();
    document.getElementById('groupIdInput').disabled = false;
    await loadGroupsTab();
    showToast('Gruppe gespeichert');
  } else showToast('Fehler', true);
}
async function deleteGroup(gid) {
  const count = kanbanEmployees.filter(function(e) { return e.group_id === gid; }).length;
  if(count > 0) {
    if(!confirm('Gruppe "' + gid + '" hat noch ' + count + ' Mitarbeiter. Diese werden in "Ohne Gruppe" verschoben. Fortfahren?')) return;
  } else {
    if(!confirm('Gruppe "' + gid + '" wirklich löschen?')) return;
  }
  const r = await fetch('/api/attendance/groups/' + gid, {method:'DELETE'});
  if(r.ok) {
    // Mitarbeiter dieser Gruppe lokal auf '' setzen und Board neu rendern
    kanbanEmployees.forEach(function(e) { if(e.group_id === gid) e.group_id = ''; });
    groupsData = groupsData.filter(function(g) { return g.id !== gid; });
    renderKanbanBoard();
    showToast('Gruppe gelöscht');
  } else showToast('Fehler', true);
}

async function removeEmployee(empId) {
  if(!confirm('Mitarbeiter aus dem Planungssystem entfernen?')) return;
  const r = await fetch('/api/attendance/employees/' + empId, {method:'DELETE'});
  if(r.ok) {
    kanbanEmployees = kanbanEmployees.filter(function(e) { return e.id !== empId; });
    renderKanbanBoard();
    showToast('Entfernt');
  } else showToast('Fehler', true);
}

// --- Quotas Tab ---
async function loadQuotas() {
  const year = document.getElementById('quotaYearSelect').value;
  const [qr, er] = await Promise.all([fetch('/api/attendance/quotas'), fetch('/api/attendance/employees')]);
  quotasData = await qr.json();
  const emps = await er.json();
  const c = document.getElementById('quotasList');
  c.innerHTML = emps.map(function(e) {
    const q = quotasData[e.id] ? (quotasData[e.id][year] !== undefined ? quotasData[e.id][year] : 30) : 30;
    return '<div class="flex items-center gap-3 bg-white rounded-xl border border-slate-200 p-3">' +
      '<span class="flex-1 text-sm text-slate-700">' + escapeHtml(e.name) + '</span>' +
      '<input type="number" min="0" max="40" value="' + q + '" id="quota-' + e.id + '" class="w-20 border border-slate-300 rounded-lg px-2 py-1 text-sm text-center">' +
      '<span class="text-xs text-slate-400">Tage/Jahr</span></div>';
  }).join('');
}
async function saveQuotas() {
  const year = document.getElementById('quotaYearSelect').value;
  const inputs = document.querySelectorAll('[id^="quota-"]');
  const updates = {};
  inputs.forEach(function(inp) {
    const empId = inp.id.replace('quota-','');
    if(!updates[empId]) updates[empId] = {};
    updates[empId][year] = parseInt(inp.value) || 30;
  });
  const r = await fetch('/api/attendance/quotas', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(updates)});
  if(r.ok) showToast('Quoten gespeichert');
  else showToast('Fehler', true);
}

// --- Holidays Tab ---
async function loadHolidays() {
  const year = document.getElementById('holidayYearSelect').value;
  const r = await fetch('/api/attendance/holidays?year=' + year);
  const data = await r.json();
  const c = document.getElementById('holidaysList');
  const cnt = document.getElementById('holidaysCount');
  if (cnt) cnt.textContent = data.length;
  c.innerHTML = data.length ? data.map(function(h) {
    return '<div class="flex items-center gap-3 bg-white rounded-xl border border-red-100 px-3 py-2.5 shadow-sm">' +
      '<div class="flex flex-col flex-1 min-w-0">' +
        '<span class="text-xs font-mono text-slate-400">' + formatDate(h.date) + '</span>' +
        '<span class="text-sm font-medium text-slate-700 truncate">' + escapeHtml(h.name) + '</span>' +
      '</div>' +
      '<span class="text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-600 flex-shrink-0">Feiertag</span>' +
    '</div>';
  }).join('') : '<p class="text-slate-400 text-sm px-1">Keine Feiertage definiert.</p>';
  loadClosures();
}

async function loadClosures() {
  const r = await fetch('/api/attendance/closures');
  const data = await r.json();
  const c = document.getElementById('closuresList');
  if (!c) return;
  const cnt = document.getElementById('closuresCount');
  if (cnt) cnt.textContent = data.length;
  const isAdmin = document.getElementById('closureDateInput') !== null;
  c.innerHTML = data.length ? data.map(function(h) {
    const del = isAdmin
      ? '<button data-action="delete-closure" data-date="' + escapeHtml(h.date) + '" class="flex-shrink-0 text-slate-300 hover:text-red-500 transition-colors" title="Löschen"><span class="material-icons-outlined text-base">delete</span></button>'
      : '';
    return '<div class="flex items-center gap-3 bg-white rounded-xl border border-orange-100 px-3 py-2.5 shadow-sm">' +
      '<div class="flex flex-col flex-1 min-w-0">' +
        '<span class="text-xs font-mono text-slate-400">' + formatDate(h.date) + '</span>' +
        '<span class="text-sm font-medium text-slate-700 truncate">' + escapeHtml(h.name) + '</span>' +
      '</div>' +
      '<span class="text-xs px-2 py-0.5 rounded-full bg-orange-100 text-orange-600 flex-shrink-0">Schließtag</span>' +
      del +
    '</div>';
  }).join('') : '<p class="text-slate-400 text-sm px-1">Keine Schließtage eingetragen.</p>';

  document.getElementById('closuresList').addEventListener('click', function(evt) {
    const btn = evt.target.closest('[data-action="delete-closure"]');
    if (btn) deleteClosure(btn.dataset.date);
  }, {once: true});
}

async function addClosure() {
  const d = document.getElementById('closureDateInput').value;
  const n = document.getElementById('closureNameInput').value.trim();
  if (!d || !n) { showToast('Datum und Bezeichnung erforderlich.'); return; }
  const r = await fetch('/api/attendance/closures', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({date: d, name: n}) });
  const res = await r.json();
  if (!r.ok) { showToast(res.error || 'Fehler'); return; }
  document.getElementById('closureDateInput').value = '';
  document.getElementById('closureNameInput').value = '';
  const form = document.getElementById('closureFormInline');
  if (form) form.classList.add('hidden');
  loadClosures();
  showToast('Schließtag eingetragen.');
}

async function deleteClosure(dateStr) {
  if (!confirm('Schließtag ' + dateStr + ' löschen?')) return;
  await fetch('/api/attendance/closures/' + dateStr, { method: 'DELETE' });
  loadClosures();
  showToast('Schließtag gelöscht.');
}

function formatDate(d) {
  if(!d) return '';
  const parts = d.split('-');
  return parts[2] + '.' + parts[1] + '.' + parts[0];
}
function escapeHtml(s) {
  return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

async function submitPasswordChange() {
  const old_pw = document.getElementById('pwOld').value;
  const new_pw = document.getElementById('pwNew').value;
  const new_pw2 = document.getElementById('pwNew2').value;
  const msg = document.getElementById('pwMsg');
  if(new_pw !== new_pw2) { msg.textContent = 'Passwörter stimmen nicht überein.'; msg.className = 'text-sm mt-2 text-red-600'; msg.classList.remove('hidden'); return; }
  try {
    const r = await fetch('/api/change_password', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({old_password:old_pw, new_password:new_pw})});
    const d = await r.json();
    if(r.ok) { document.getElementById('passwordModal').style.display = 'none'; showToast('Passwort geändert'); }
    else { msg.textContent = d.error || 'Fehler'; msg.className = 'text-sm mt-2 text-red-600'; msg.classList.remove('hidden'); }
  } catch(e) { msg.textContent = 'Netzwerkfehler'; msg.className = 'text-sm mt-2 text-red-600'; msg.classList.remove('hidden'); }
}

// Sidebar toggle
(function() {
  const sidebar = document.getElementById('sidebar');
  const toggleBtn = document.getElementById('sidebar-toggle');
  if(sidebar && toggleBtn) {
    const icon = toggleBtn.querySelector('.material-icons-outlined');
    const applyState = (collapsed) => {
      if(collapsed) { sidebar.classList.add('is-collapsed'); if(icon) icon.textContent = 'menu'; }
      else { sidebar.classList.remove('is-collapsed'); if(icon) icon.textContent = 'chevron_left'; }
    };
    toggleBtn.addEventListener('click', function() {
      const nowCollapsed = !sidebar.classList.contains('is-collapsed');
      localStorage.setItem('sidebarCollapsed', nowCollapsed);
      applyState(nowCollapsed);
    });
    applyState(localStorage.getItem('sidebarCollapsed') === 'true');
  }
})();

// Init
(function init() {
  const curYear = new Date().getFullYear();
  ['quotaYearSelect','holidayYearSelect'].forEach(function(id) {
    const sel = document.getElementById(id);
    for(let y = curYear-1; y <= curYear+2; y++) {
      const o = document.createElement('option');
      o.value = y; o.textContent = y;
      if(y === curYear) o.selected = true;
      sel.appendChild(o);
    }
  });
  loadData();
})();
</script>
<!-- Shift Model Library Modal -->
<div id="smModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-40 p-4">
  <div class="bg-white rounded-xl shadow-2xl w-full flex flex-col" style="max-width:720px;max-height:90vh">
    <div class="flex items-center justify-between px-5 py-3 border-b border-slate-200">
      <h2 class="text-base font-semibold text-slate-800">Schichtmodell-Bibliothek</h2>
      <button onclick="smClose()" class="text-slate-400 hover:text-slate-700 text-xl leading-none">&times;</button>
    </div>
    <div class="flex flex-1 min-h-0">
      <!-- Library list -->
      <div class="w-44 border-r border-slate-200 flex flex-col flex-shrink-0">
        <div id="smLibList" class="flex-1 overflow-y-auto py-1"></div>
        <div class="border-t border-slate-200 p-2">
          <button onclick="smNewModel()" class="w-full text-xs text-blue-600 hover:text-blue-800 font-medium py-1">+ Neues Modell</button>
        </div>
      </div>
      <!-- Editor -->
      <div class="flex-1 flex flex-col p-4 overflow-y-auto">
        <div id="smEditorEmpty" class="flex-1 flex items-center justify-center text-slate-400 text-sm">Modell auswählen oder neu erstellen</div>
        <div id="smEditor" class="hidden flex-col gap-3">
          <div>
            <label class="text-xs font-medium text-slate-600">Name</label>
            <input id="smNameInput" type="text" placeholder="z.B. 2+2 Block F/S" class="mt-1 w-full border border-slate-200 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-300">
          </div>
          <div>
            <label class="text-xs font-medium text-slate-600">Muster (Wochen-Zyklus — klicken zum Ändern)</label>
            <div class="mt-1 flex flex-wrap gap-1.5 items-center" id="smPatternSlots"></div>
            <button onclick="smAddSlot()" class="mt-1.5 text-xs text-blue-600 hover:text-blue-800 font-medium">+ Woche hinzufügen</button>
          </div>
          <div>
            <label class="text-xs font-medium text-slate-600">Vorschau (nächste 8 Wochen ab Referenz)</label>
            <div id="smPreview" class="mt-1 flex flex-wrap gap-1"></div>
          </div>
          <div class="flex gap-2 pt-1 border-t border-slate-100 flex-wrap">
            <button onclick="smSave()" class="px-3 py-1.5 bg-blue-600 text-white text-xs font-medium rounded hover:bg-blue-700">Speichern</button>
            <button id="smDeleteBtn" onclick="smDelete()" class="px-3 py-1.5 bg-red-50 text-red-600 text-xs font-medium rounded hover:bg-red-100 hidden">Löschen</button>
            <div class="flex-1"></div>
            <button id="smApplyBtn" onclick="smApplyToEmployee()" class="px-3 py-1.5 bg-green-600 text-white text-xs font-medium rounded hover:bg-green-700 hidden">Auf Mitarbeiter anwenden &#9654;</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
<script>
// ── Shift Model Library Modal ─────────────────────────────────
let smEmpId = null;
let smCurrentId = null;
let smPattern = [];
let smLibrary = [];
const SM_SHIFTS = ['F','S','N',''];
const SM_LABELS = {'F':'F','S':'S','N':'N','':'–'};
const SM_COLORS = {'F':'bg-blue-100 text-blue-700','S':'bg-amber-100 text-amber-700','N':'bg-green-100 text-green-700','':'bg-slate-100 text-slate-400'};

function smOpen(empId) {
  smEmpId = empId || null;
  smCurrentId = null;
  smPattern = [];
  document.getElementById('smApplyBtn').classList.toggle('hidden', !empId);
  document.getElementById('smModal').classList.remove('hidden');
  smLoadLibrary();
}

function smClose() {
  document.getElementById('smModal').classList.add('hidden');
}

async function smLoadLibrary() {
  const r = await fetch('/api/attendance/shift_model_library');
  smLibrary = await r.json();
  smRenderList();
  if (smLibrary.length > 0 && !smCurrentId) smSelectModel(smLibrary[0].id);
  else if (smLibrary.length === 0) smShowEditor(false);
}

function smRenderList() {
  const el = document.getElementById('smLibList');
  el.innerHTML = smLibrary.map(function(m) {
    const active = m.id === smCurrentId ? 'bg-blue-50 text-blue-700 font-semibold' : 'text-slate-700 hover:bg-slate-50';
    return '<div class="px-3 py-2 text-xs cursor-pointer ' + active + '" data-sm-id="' + m.id + '" data-action="sm-select">' +
      '<div class="font-medium truncate">' + escapeHtml(m.name) + '</div>' +
      '<div class="text-slate-400">[' + m.pattern.join('\xB7') + ']</div>' +
    '</div>';
  }).join('');
}

document.getElementById('smLibList').addEventListener('click', function(evt) {
  const el = evt.target.closest('[data-action="sm-select"]');
  if (el) smSelectModel(el.dataset.smId);
});

function smSelectModel(id) {
  const m = smLibrary.find(function(x) { return x.id === id; });
  if (!m) return;
  smCurrentId = id;
  smPattern = m.pattern.slice();
  document.getElementById('smNameInput').value = m.name;
  document.getElementById('smDeleteBtn').classList.remove('hidden');
  smShowEditor(true);
  smRenderList();
  smRenderSlots();
  smRenderPreview();
}

function smNewModel() {
  smCurrentId = null;
  smPattern = ['F','S'];
  document.getElementById('smNameInput').value = '';
  document.getElementById('smDeleteBtn').classList.add('hidden');
  smRenderList();
  smShowEditor(true);
  smRenderSlots();
  smRenderPreview();
}

function smShowEditor(show) {
  document.getElementById('smEditor').classList.toggle('hidden', !show);
  document.getElementById('smEditorEmpty').classList.toggle('hidden', show);
  if (show) document.getElementById('smEditor').classList.add('flex');
}

function smRenderSlots() {
  const el = document.getElementById('smPatternSlots');
  el.innerHTML = smPattern.map(function(s, idx) {
    return '<div class="flex flex-col items-center gap-0.5">' +
      '<span class="text-slate-400" style="font-size:9px">W' + (idx+1) + '</span>' +
      '<button onclick="smCycleSlot(' + idx + ')" class="w-9 h-9 rounded-lg font-bold text-sm ' + (SM_COLORS[s] || SM_COLORS['']) + '">' + (SM_LABELS[s] || '–') + '</button>' +
      '<button onclick="smRemoveSlot(' + idx + ')" class="text-slate-300 hover:text-red-400" style="font-size:10px">&times;</button>' +
    '</div>';
  }).join('');
}

function smCycleSlot(idx) {
  const cur = smPattern[idx] || '';
  smPattern[idx] = SM_SHIFTS[(SM_SHIFTS.indexOf(cur) + 1) % SM_SHIFTS.length];
  smRenderSlots();
  smRenderPreview();
}

function smRemoveSlot(idx) {
  if (smPattern.length <= 1) return;
  smPattern.splice(idx, 1);
  smRenderSlots();
  smRenderPreview();
}

function smAddSlot() {
  smPattern.push('F');
  smRenderSlots();
  smRenderPreview();
}

function smRenderPreview() {
  const el = document.getElementById('smPreview');
  if (!smPattern.length) { el.innerHTML = ''; return; }
  const smColors2 = {F:'bg-blue-100 text-blue-700',S:'bg-amber-100 text-amber-700',N:'bg-green-100 text-green-700','':'bg-slate-100 text-slate-400'};
  el.innerHTML = Array.from({length:8}, function(_, i) {
    const s = smPattern[i % smPattern.length] || '';
    return '<span class="text-xs px-2 py-0.5 rounded font-medium ' + (smColors2[s] || smColors2['']) + '">W' + (i+1) + ':' + (s||'–') + '</span>';
  }).join('');
}

async function smSave() {
  const name = document.getElementById('smNameInput').value.trim();
  if (!name) { showToast('Bitte einen Namen eingeben', true); return; }
  if (!smPattern.length) { showToast('Bitte mindestens eine Woche definieren', true); return; }
  const body = {name: name, pattern: smPattern};
  let r;
  if (smCurrentId) {
    r = await fetch('/api/attendance/shift_model_library/' + smCurrentId, {method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)});
  } else {
    r = await fetch('/api/attendance/shift_model_library', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)});
  }
  const data = await r.json();
  if (data.ok) {
    showToast('Modell gespeichert');
    if (!smCurrentId && data.id) smCurrentId = data.id;
    smLoadLibrary();
    loadData(true);
  } else {
    showToast(data.error || 'Fehler', true);
  }
}

async function smDelete() {
  if (!smCurrentId) return;
  if (!confirm('Modell wirklich löschen?')) return;
  const r = await fetch('/api/attendance/shift_model_library/' + smCurrentId, {method:'DELETE'});
  const data = await r.json();
  if (data.ok) {
    smCurrentId = null;
    smPattern = [];
    showToast('Modell gelöscht');
    smLoadLibrary();
    loadData(true);
  }
}

async function smApplyToEmployee() {
  if (!smEmpId || !smCurrentId) { showToast('Kein Mitarbeiter oder Modell gewählt', true); return; }
  if (!planningData || !planningData.weeks || !planningData.weeks.length) return;
  const refWeek = planningData.weeks[0].week_key;
  const r = await fetch('/api/attendance/employees/' + encodeURIComponent(smEmpId) + '/shift_model', {
    method: 'PUT',
    headers: {'Content-Type':'application/json'},
    body: JSON.stringify({shift_model: smCurrentId, ref_week: refWeek, ref_shift: ''})
  });
  const data = await r.json();
  if (!data.ok) { showToast(data.error || 'Fehler', true); return; }
  // Fill visible weeks as pending changes
  if (!pendingChanges[smEmpId]) pendingChanges[smEmpId] = {};
  planningData.weeks.forEach(function(week, idx) {
    if (!pendingChanges[smEmpId][week.week_key]) pendingChanges[smEmpId][week.week_key] = {days:{}};
    pendingChanges[smEmpId][week.week_key].shift = smPattern[idx % smPattern.length] || '';
  });
  showToast('Modell angewendet ab ' + fmtRefWeek(refWeek) + ' — bitte speichern');
  smClose();
  loadData(true);
}
</script>
</div>
</div>
</div>
</main>
</div>

<!-- Floating Save Button (nur Planung-Tab, nur für Editoren) -->
{% if can_edit %}
<button id="saveFab" onclick="handleFabClick()" class="fab-locked">
  <span class="material-icons-outlined fab-icon" style="font-size:1.1rem;">lock_open</span>
  <span class="fab-label">Bearbeiten</span>
</button>
{% endif %}

</body>
</html>
"""

EMPLOYEE_PRESENCE_HTML_CONTENT = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Inter%3Awght@400%3B500%3B700%3B900&family=Noto+Sans%3Awght@400%3B500%3B700%3B900" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <title>MBET - Anwesenheitsübersicht</title>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        body { font-family: 'Inter', "Noto Sans", sans-serif; }
        .material-icons-outlined { font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; }
        .presence-table-fixed th, .presence-table-fixed td { @apply px-4 py-4 text-sm; vertical-align: middle; }

        .presence-table-scrollable th, .presence-table-scrollable td { 
            @apply px-2 py-4 text-sm whitespace-nowrap align-middle; 
            text-align: center !important;
            min-width: 60px; max-width: 60px; 
        }
        .presence-table-scrollable th { @apply font-semibold text-slate-600; }

        .presence-table-scrollable th.kw-header { background-color: #F8FAFC; color: #475569; }
        .presence-table-scrollable td.kw-cell { background-color: #F8FAFC; font-weight: 500; color: #4B5563; }
        .day-cell.is-today { background-color: #FEF3C7 !important; } 
        .day-cell.is-weekend:not(.is-today) { background-color: #F8FAFC; } 
        .day-cell.is-holiday { background: #DBEAFE !important; color: #1E40AF !important; } 
        .day-cell.has-event { background: repeating-linear-gradient(45deg, #dbeafe, #dbeafe 10px, #bfdbfe 10px, #bfdbfe 20px) !important; color: #1E3A8A; font-weight: 600; }

        .sticky-header-presence th { position: sticky; top: 0; background-color: #fff; z-index: 15; border-bottom: 1px solid #e2e8f0; }
        .presence-table-fixed .sticky-col-presence { position: sticky; left: 0; background-color: #fff; z-index: 5; }
        .presence-table-fixed .sticky-header-presence th.sticky-col-presence { z-index: 20; background-color: #fff; }
        .presence-table-fixed tbody tr:hover .sticky-col-presence { background-color: #F8FAFC; }
        .presence-table-fixed .sticky-col-presence { border-right: 1px solid #e2e8f0; }
        .group-separator-row td { @apply px-4 py-2 text-left text-sm font-semibold; background-color: #f1f5f9; color: #334155; position: sticky; left: 0; z-index: 6; }

        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
            <h1 class="text-2xl font-bold tracking-tight text-slate-800">Anwesenheitsübersicht</h1>
            <div class="flex items-center gap-3">
                <div class="relative">
                    <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">search</span>
                    <input id="employeePresenceSearchInput" class="w-64 pl-10 pr-4 py-2 rounded-lg border border-gray-300 bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Mitarbeiter suchen..." type="text"/>
                </div>
            </div>
        </header>
        <div class="p-4 sm:p-6 lg:p-8">
                {% if session.get('is_admin') %}
                <div id="upload-container" class="mb-6">
                     <div id="upload-drop-zone" class="flex justify-center items-center w-full px-6 py-10 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:border-blue-500 hover:bg-gray-50 transition-colors duration-200">
                        <div class="text-center">
                            <span class="material-icons-outlined text-4xl text-gray-400">upload_file</span>
                            <p class="mt-2 text-sm text-gray-600"><span class="font-semibold">Neue Anwesenheits-CSV hierher ziehen</span> oder klicken zum Auswählen</p>
                            <p class="text-xs text-gray-500 mt-1">Die bestehende `MBET_employees.csv` wird vollständig ersetzt.</p>
                        </div>
                        <input type="file" id="file-input" class="hidden" accept=".csv">
                    </div>
                    <div id="upload-status" class="mt-2 text-sm text-center"></div>
                </div>
                {% endif %}

            <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
                <div>
                    <p class="text-gray-600 mt-1 text-sm"></p>
                </div>
                <div class="flex items-center gap-3 mt-4 md:mt-0">
                    <button id="currentWeekBtn" class="rounded-full bg-white px-3 py-3 text-sm font-semibold text-slate-700 shadow-sm ring-1 ring-inset ring-slate-200 hover:bg-slate-50" title="Zur aktuellen Woche springen">
                        Heute
                    </button>
                    <div class="flex items-center rounded-full ring-1 ring-slate-200 bg-white shadow-sm">
                        <button id="prevWeekBtn" class="px-4 py-2 text-slate-500 hover:bg-slate-100 rounded-l-full disabled:opacity-50">
                            <span class="material-icons-outlined">chevron_left</span>
                        </button>
                        <span id="timelineRangeLabel" class="text-sm font-medium text-slate-700 px-4 w-32 text-center border-l border-r border-slate-200">
                            2 Wochen
                        </span>
                        <button id="nextWeekBtn" class="px-3 py-2 text-slate-500 hover:bg-slate-100 rounded-r-full disabled:opacity-50">
                            <span class="material-icons-outlined">chevron_right</span>
                        </button>
                    </div>
                </div>
            </div>

            <div id="presenceTableContainer" class="flex rounded-lg border border-slate-200 overflow-hidden bg-white">
                <p class="text-center py-10 text-gray-500 w-full">Lade Anwesenheitsdaten...</p>
            </div>
            <div>
                <p class="text-gray-600 mt-1 text-sm">Zuletzt geladen aus MBET_employees.csv: <span id="lastLoadedTimestamp">{{ presence_data.last_loaded }}</span></p>
            </div>
        </div>
    </main>
</div>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    toggleBtnIcon.textContent = 'chevron_left';
                }
            };
            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
        }

    const tableContainer = document.getElementById('presenceTableContainer');
    const lastLoadedTimestampEl = document.getElementById('lastLoadedTimestamp');
    const prevWeekBtn = document.getElementById('prevWeekBtn');
    const currentWeekBtn = document.getElementById('currentWeekBtn');
    const nextWeekBtn = document.getElementById('nextWeekBtn');
    const searchInput = document.getElementById('employeePresenceSearchInput');

    let currentOffset = 0;
    const numWeeksToDisplay = 2;
    let minDateInCsv = null;
    let maxDateInCsv = null;
    const dropZone = document.getElementById('upload-drop-zone');
    const fileInput = document.getElementById('file-input');
    const statusDiv = document.getElementById('upload-status');

    if (dropZone) {
        dropZone.addEventListener('click', () => fileInput.click());
        fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) handleFileUpload(e.target.files[0]); });
        dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('border-blue-500', 'bg-blue-50'); });
        dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('border-blue-500', 'bg-blue-50'); });
        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            dropZone.classList.remove('border-blue-500', 'bg-blue-50');
            if (e.dataTransfer.files.length > 0) handleFileUpload(e.dataTransfer.files[0]);
        });
        async function handleFileUpload(file) {
            if (!file.name.toLowerCase().endsWith('.csv')) {
                statusDiv.textContent = 'Fehler: Es werden nur .csv-Dateien akzeptiert.';
                statusDiv.className = 'mt-2 text-sm text-center text-red-600'; return;
            }
            statusDiv.textContent = 'Datei wird hochgeladen und verarbeitet...';
            statusDiv.className = 'mt-2 text-sm text-center text-blue-600';
            const formData = new FormData();
            formData.append('presence_file', file);
            try {
                const response = await fetch("{{ url_for('api_upload_employee_presence') }}", { method: 'POST', body: formData });
                const result = await response.json();
                if (response.ok) {
                    statusDiv.textContent = result.message;
                    statusDiv.className = 'mt-2 text-sm text-center text-green-600';
                    setTimeout(() => { location.reload(); }, 2000);
                } else { throw new Error(result.error || 'Unbekannter Fehler beim Upload.'); }
            } catch (error) {
                statusDiv.textContent = `Fehler: ${error.message}`;
                statusDiv.className = 'mt-2 text-sm text-center text-red-600';
            }
        }
    }

    async function fetchAndRenderPresenceData(offset) {
        tableContainer.innerHTML = '<p class="text-center py-10 text-gray-500 w-full">Lade Anwesenheitsdaten...</p>';
        try {
            const response = await fetch(`{{ url_for('api_get_employee_presence_data') }}?offset=${offset}&num_weeks=${numWeeksToDisplay}`);
            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
            const data = await response.json();
            currentOffset = data.current_week_offset;
            minDateInCsv = data.min_date_in_csv_iso ? new Date(data.min_date_in_csv_iso) : null;
            maxDateInCsv = data.max_date_in_csv_iso ? new Date(data.max_date_in_csv_iso) : null;
            renderPresenceTable(data);
            if (lastLoadedTimestampEl) lastLoadedTimestampEl.textContent = data.last_loaded;
            updateNavButtonStates();
        } catch (error) {
            console.error("Fehler beim Laden der Anwesenheitsdaten:", error);
            tableContainer.innerHTML = '<p class="text-center py-10 text-red-500 w-full">Fehler beim Laden der Daten.</p>';
        }
    }

    function updateNavButtonStates() {
        prevWeekBtn.disabled = false; nextWeekBtn.disabled = false;
        function timedeltaDays(days) { return new Date(new Date().setDate(new Date().getDate() + days) - new Date()); }
        if (minDateInCsv && numWeeksToDisplay > 0) {
            const currentDisplayStartDate = new Date();
            currentDisplayStartDate.setDate(currentDisplayStartDate.getDate() - currentDisplayStartDate.getDay() + 1 + (currentOffset * 7));
            const prevDisplayPotentialStart = new Date(currentDisplayStartDate);
            prevDisplayPotentialStart.setDate(prevDisplayPotentialStart.getDate() - 7);
            if (prevDisplayPotentialStart < (minDateInCsv - timedeltaDays(minDateInCsv.getDay())) ) {
                 prevWeekBtn.disabled = true;
            }
        }
        if (maxDateInCsv && numWeeksToDisplay > 0) {
            const currentDisplayEndDate = new Date();
            currentDisplayEndDate.setDate(currentDisplayEndDate.getDate() - currentDisplayEndDate.getDay() + (currentOffset * 7) + ((numWeeksToDisplay -1) * 7) + 6 );
            const nextDisplayPotentialEnd = new Date(currentDisplayEndDate);
            nextDisplayPotentialEnd.setDate(nextDisplayPotentialEnd.getDate() + 7);
             if (nextDisplayPotentialEnd > (maxDateInCsv + timedeltaDays(6-maxDateInCsv.getDay())) ) {
                 nextWeekBtn.disabled = true;
            }
        }
    }

function renderPresenceTable(data) {
        tableContainer.innerHTML = '';
        if (!data.employee_groups || data.employee_groups.length === 0) {
            tableContainer.innerHTML = '<p class="text-center py-10 text-gray-500 w-full">Keine Anwesenheitsdaten für diesen Zeitraum.</p>';
            return;
        }

        const fixedTableDiv = document.createElement('div');
        fixedTableDiv.className = 'flex-shrink-0';
        const fixedTable = document.createElement('table');
        fixedTable.className = 'presence-table-fixed';
        fixedTableDiv.appendChild(fixedTable);
        const fixedThead = fixedTable.createTHead();
        fixedThead.className = 'sticky-header-presence';
        const fixedHeaderRow = fixedThead.insertRow();
        fixedHeaderRow.innerHTML = `<th class="sticky-col-presence text-left w-48">Name</th><th class="sticky-col-presence text-left w-24" style="left: 12rem;">Initialen</th>`;
        const fixedTbody = fixedTable.createTBody();
        fixedTbody.className = 'divide-y divide-gray-200 bg-white';

        const scrollableTableWrapper = document.createElement('div');
        scrollableTableWrapper.className = 'flex-grow overflow-x-auto';
        const scrollableTable = document.createElement('table');
        scrollableTable.className = 'min-w-full presence-table-scrollable';
        scrollableTableWrapper.appendChild(scrollableTable);
        const scrollableThead = scrollableTable.createTHead();
        scrollableThead.className = 'sticky-header-presence';
        const scrollableHeaderRow = scrollableThead.insertRow();
        data.headers.forEach(header_col => {
            const th = document.createElement('th');
            th.className = `${header_col.type === 'kw' ? 'kw-header' : ('day-header ' + (header_col.is_today ? 'is-today' : '') + (header_col.is_weekend && !header_col.is_today ? ' is-weekend' : ''))}`;
            th.textContent = header_col.text;
            scrollableHeaderRow.appendChild(th);
        });
        const scrollableTbody = scrollableTable.createTBody();
        scrollableTbody.className = 'divide-y divide-gray-200 bg-white';

        data.employee_groups.forEach(group => {
            const fixedGroupRow = fixedTbody.insertRow();
            fixedGroupRow.className = 'group-separator-row';
            fixedGroupRow.dataset.groupName = group.group_name;
            const fixedGroupCell = fixedGroupRow.insertCell();
            fixedGroupCell.colSpan = 2;
            fixedGroupCell.textContent = group.group_name;

            const scrollableGroupRow = scrollableTbody.insertRow();
            scrollableGroupRow.className = 'group-separator-row';
            scrollableGroupRow.dataset.groupName = group.group_name;
            const scrollableGroupCell = scrollableGroupRow.insertCell();
            scrollableGroupCell.colSpan = data.headers.length;
            scrollableGroupCell.innerHTML = `<span style="visibility: hidden;">${group.group_name}</span>`;

            group.employees.forEach(emp => {
                const fixedEmpRow = fixedTbody.insertRow();
                fixedEmpRow.className = 'hover:bg-gray-50 employee-data-row';
                fixedEmpRow.dataset.groupBelongsTo = group.group_name;
                fixedEmpRow.innerHTML = `
                    <td class="sticky-col-presence font-medium w-48">
                        <div class="truncate" title="${emp.name}">${emp.name}</div>
                    </td>
                    <td class="sticky-col-presence w-24" style="left: 12rem;">${emp.initials}</td>
                `;

                const scrollableEmpRow = scrollableTbody.insertRow();
                scrollableEmpRow.className = 'hover:bg-gray-50 employee-data-row';
                scrollableEmpRow.dataset.groupBelongsTo = group.group_name;
                emp.presence.forEach((presence_item, index) => {
                    const cell = scrollableEmpRow.insertCell();
                    if (presence_item.type === 'kw') {
                        cell.className = 'kw-cell';
                        cell.textContent = presence_item.shift;
                    } else if (presence_item.type === 'day') {
                        const day_header = data.headers[index];
                        cell.className = `day-cell ${day_header.is_today ? 'is-today' : ''} ${day_header.is_weekend && !day_header.is_today ? 'is-weekend' : ''} ${presence_item.is_holiday_from_data ? 'is-holiday' : ''} ${presence_item.event ? 'has-event' : ''}`;
                        cell.title = presence_item.event ? presence_item.event : (day_header.is_weekend ? 'Wochenende' : 'Anwesend');
                        cell.textContent = presence_item.event || ' '; 
                    } else { cell.textContent = '?'; }
                });
            });
        });

        tableContainer.appendChild(fixedTableDiv);
        tableContainer.appendChild(scrollableTableWrapper);
        synchronizeRowHeights();
        scrollToRelevantPosition(scrollableTableWrapper, scrollableTable);
        applySearchFilter(searchInput.value);
    }

    function synchronizeRowHeights() {
        const fixedTableBody = document.querySelector('.presence-table-fixed tbody');
        const scrollableTableBody = document.querySelector('.presence-table-scrollable tbody');
        if (fixedTableBody && scrollableTableBody) {
            const fixedRows = fixedTableBody.querySelectorAll('tr');
            const scrollableRows = scrollableTableBody.querySelectorAll('tr');
            const numRows = Math.min(fixedRows.length, scrollableRows.length);
            for (let i = 0; i < numRows; i++) {
                fixedRows[i].style.height = ''; scrollableRows[i].style.height = '';
            }
            for (let i = 0; i < numRows; i++) {
                const fixedRowHeight = fixedRows[i].offsetHeight;
                const scrollableRowHeight = scrollableRows[i].offsetHeight;
                const maxHeight = Math.max(fixedRowHeight, scrollableRowHeight);
                fixedRows[i].style.height = maxHeight + 'px';
                scrollableRows[i].style.height = maxHeight + 'px';
            }
        }
    }

    function scrollToRelevantPosition(scrollWrapper, scrollableTable) {
        if (scrollWrapper && scrollableTable) {
            const todayHeaderCell = scrollableTable.querySelector('thead th.is-today');
            if (todayHeaderCell) {
                const containerWidth = scrollWrapper.offsetWidth;
                const scrollTarget = todayHeaderCell.offsetLeft - (containerWidth / 4);
                scrollWrapper.scrollLeft = Math.max(0, scrollTarget);
            } else {
                 const firstKwHeader = scrollableTable.querySelector('thead th.kw-header');
                 if(firstKwHeader) {
                    scrollWrapper.scrollLeft = Math.max(0, firstKwHeader.offsetLeft - 20);
                 }
            }
        }
    }

    function applySearchFilter(searchTerm) {
        const term = searchTerm.toLowerCase();
        const fixedTableBody = document.querySelector('.presence-table-fixed tbody');
        const scrollableTableBody = document.querySelector('.presence-table-scrollable tbody');
        if (!fixedTableBody || !scrollableTableBody) return;
        const fixedRows = fixedTableBody.querySelectorAll('tr');
        const scrollableRows = scrollableTableBody.querySelectorAll('tr');
        fixedRows.forEach((fixedRow, index) => {
            const scrollableRow = scrollableRows[index];
            if (!scrollableRow) return;
            if (fixedRow.classList.contains('employee-data-row')) {
                const nameCell = fixedRow.cells[0];
                const initialsCell = fixedRow.cells[1];
                let matchesSearch = false;
                if (nameCell && nameCell.textContent.toLowerCase().includes(term)) matchesSearch = true;
                if (!matchesSearch && initialsCell && initialsCell.textContent.toLowerCase().includes(term)) matchesSearch = true;
                fixedRow.style.display = (matchesSearch || !term) ? '' : 'none';
                scrollableRow.style.display = (matchesSearch || !term) ? '' : 'none';
            }
        });
        fixedRows.forEach((fixedRow, index) => {
            if (fixedRow.classList.contains('group-separator-row')) {
                let nextSibling = fixedRow.nextElementSibling;
                let groupHasVisibleDataRows = false;
                while(nextSibling && nextSibling.classList.contains('employee-data-row')) {
                    if (nextSibling.style.display !== 'none') {
                        groupHasVisibleDataRows = true;
                        break;
                    }
                    nextSibling = nextSibling.nextElementSibling;
                }
                fixedRow.style.display = (groupHasVisibleDataRows || !term) ? '' : 'none';
                scrollableRows[index].style.display = (groupHasVisibleDataRows || !term) ? '' : 'none';
            }
        });
        synchronizeRowHeights();
    }

    if (searchInput) {
        searchInput.addEventListener('input', (event) => applySearchFilter(event.target.value));
    }
    prevWeekBtn.addEventListener('click', () => { fetchAndRenderPresenceData(currentOffset - 1); });
    currentWeekBtn.addEventListener('click', () => { fetchAndRenderPresenceData(0); });
    nextWeekBtn.addEventListener('click', () => { fetchAndRenderPresenceData(currentOffset + 1); });
    window.addEventListener('resize', synchronizeRowHeights);
    fetchAndRenderPresenceData(currentOffset);
});
</script>
</body></html>
"""

CONSUMABLE_ORDER_PAGE_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
    <title>MBET - Bestellsystem</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .text-primary { color: #3B82F6; }
        .bg-primary { background-color: #3B82F6; }
        .text-slate-text { color: #334155; }
        .border-slate-border { border-color: #cbd5e1; }
        .custom-scrollbar::-webkit-scrollbar { width: 8px; height: 8px; }
        .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
        .custom-scrollbar::-webkit-scrollbar-thumb {
            background: transparent;
            border-radius: 10px;
            transition: background-color 0.2s ease-in-out;
        }
        .custom-scrollbar:hover::-webkit-scrollbar-thumb {
            background: #9ca3af;
        }
        .scroll-fade {
            -webkit-mask-image: linear-gradient(to bottom, black 90%, transparent 100%);
            mask-image: linear-gradient(to bottom, black 90%, transparent 100%);
        }

        .status-badge { padding: 0.125rem 0.625rem; font-size: 0.75rem; font-weight: 500; border-radius: 9999px; display: inline-flex; align-items: center; text-transform: capitalize; }
        .status-requested { background-color: #f1f5f9; color: #475569; }
        .status-inprogress { background-color: #dbeafe; color: #2563eb; }
        .status-completed { background-color: #dcfce7; color: #166534; }

        .kanban-column { display: flex; flex-direction: column; background-color: #e5e7eb; border-radius: 0.75rem; padding: 1rem; }
        .kanban-card-container { flex-grow: 1; min-height: 200px; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; height: 24rem; }
        .kanban-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
        .kanban-card { background-color: #ffffff; border-radius: 0.5rem; padding: 0.75rem; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); cursor: grab; transition: all 0.2s ease-in-out; position: relative; }
        .kanban-card:active { cursor: grabbing; }
        .kanban-card:hover { box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); transform: translateY(-1px); }
        .kanban-card.opacity-50 { background-color: #eff6ff; outline: 2px solid #3b82f6; }

        .kanban-card.is-critical { border: 1px solid #f87171; background-color: #fef2f2; }
        .critical-icon { position: absolute; top: 0.5rem; right: 0.5rem; font-size: 1rem; color: #ef4444; }

        .modal { transition: opacity 0.25s ease; }
        .modal-container { max-height: 90vh; display: flex; flex-direction: column; }
        body.modal-active { overflow-y: hidden; }

        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        
        .readonly-input { background-color: #f8fafc; color: #64748b; cursor: default; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50 text-slate-text">
        <header class="flex items-center justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Bestellsystem für Verbrauchsmaterialien</h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">
            <div class="mb-12">
                <h2 class="text-2xl font-semibold text-slate-800 mb-6">Bestellübersicht</h2>
                <div id="kanban-container-parent" class="grid grid-cols-1 md:grid-cols-3 gap-6">
                    <div class="kanban-column" data-status="requested"><div class="kanban-header"><h3 class="font-semibold text-slate-700">Notwendig</h3><button class="text-primary hover:text-blue-700" onclick="openModal('templateModal')"><span class="material-icons-outlined">add_circle</span></button></div><div class="kanban-card-container custom-scrollbar"></div></div>
                    <div class="kanban-column" data-status="inprogress"><div class="kanban-header"><h3 class="font-semibold text-slate-700">In Bearbeitung</h3></div><div class="kanban-card-container custom-scrollbar"></div></div>
                    <div class="kanban-column" data-status="completed"><div class="kanban-header"><h3 class="font-semibold text-slate-700">Abgeschlossen</h3></div><div class="kanban-card-container custom-scrollbar"></div></div>
                </div>
            </div>

            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-28">
                <div class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4"><div class="p-3 bg-gray-100 rounded-full"><span class="material-icons-outlined text-gray-500 text-3xl">inbox</span></div><div><p class="text-sm text-gray-500">Notwendig</p><p id="kpi-requested" class="text-2xl font-bold text-slate-800">0</p></div></div>
                <div class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4"><div class="p-3 bg-blue-100 rounded-full"><span class="material-icons-outlined text-blue-500 text-3xl">autorenew</span></div><div><p class="text-sm text-gray-500">In Bearbeitung</p><p id="kpi-inprogress" class="text-2xl font-bold text-slate-800">0</p></div></div>
                <div class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4"><div class="p-3 bg-red-100 rounded-full"><span class="material-icons-outlined text-red-500 text-3xl">error</span></div><div><p class="text-sm text-gray-500">Kritisch & Offen</p><p id="kpi-critical" class="text-2xl font-bold text-slate-800">0</p></div></div>
                <div id="kpi-expiry-tile" class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4 transition-all duration-300 cursor-pointer hover:shadow-lg hover:-translate-y-0.5" onclick="switchToExpiryView()"><div id="kpi-expiry-icon-container" class="p-3 bg-orange-100 rounded-full transition-colors duration-300"><span class="material-icons-outlined text-orange-500 text-3xl">calendar_today</span></div><div><p class="text-sm text-gray-500">Artikel mit MHD</p><p id="kpi-expiry" class="text-2xl font-bold text-slate-800">0</p></div></div>
            </div>

            <div id="table-view" class="">
                <div class="mb-6">
                    <nav id="viewTabs" class="-mb-px flex space-x-8 border-b border-slate-200">
                        <button data-view="table" class="tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-primary text-primary">Alle Bestellungen</button>
                        <button data-view="templates" class="tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Bestellvorlagen</button>
                        <button data-view="expiry" class="tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Verfallsdaten</button>
                        <button data-view="analyse" class="tab-btn whitespace-nowrap border-b-2 px-1 pb-3 text-sm font-medium border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700">Analyse</button>
                    </nav>
                </div>
                <div id="tableContainer" class="bg-white rounded-lg shadow p-6">
                    <div class="flex flex-col sm:flex-row justify-between items-center mb-6 gap-4"><h2 class="text-xl font-semibold text-slate-800">Bestellhistorie</h2><div class="relative w-full sm:w-64"><span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">search</span><input id="tableSearchInput" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 bg-white focus:outline-none focus:ring-2 focus:ring-primary" placeholder="Tabelle durchsuchen..." type="text"/></div></div>
                    <div class="overflow-x-auto custom-scrollbar"><table class="w-full min-w-[1200px] text-sm text-left text-slate-text"><thead class="text-xs text-gray-700 uppercase bg-gray-50">
                        <tr>
                            <th class="px-6 py-3 rounded-l-lg" scope="col">ID</th>
                            <th class="px-6 py-3" scope="col">Artikel</th>
                            <th class="px-6 py-3" scope="col">Menge</th>
                            <th class="px-6 py-3" scope="col">Einheit</th>
                            <th class="px-6 py-3" scope="col">Angefordert</th>
                            <th class="px-6 py-3" scope="col">Angefordert am</th>
                            <th class="px-6 py-3" scope="col">Status</th>
                            <th class="px-6 py-3" scope="col">Bestellt am</th>
                            <th class="px-6 py-3" scope="col">BANF Nr.</th>
                            <th class="px-6 py-3" scope="col">Lieferung</th>
                            <th class="px-6 py-3" scope="col">Doku.</th>
                            <th class="px-6 py-3 rounded-r-lg" scope="col"></th>
                        </tr>
                    </thead><tbody id="orderHistoryTableBody"></tbody></table></div>
                    <div id="table-footer" class="text-center mt-4"></div>
                </div>
                <div id="analyseContainer" class="bg-slate-50 rounded-lg p-0 md:p-6 hidden">
                    <div class="mb-6 px-6 md:px-0">
                        <h2 class="text-2xl font-bold text-slate-900 mb-2">Bestellanalyse</h2>
                        <p class="text-sm text-slate-600">Übersicht der Bestellvorgänge und -zeiten.</p>
                    </div>
                    <div id="analyseChartsContainer" class="grid grid-cols-1 lg:grid-cols-3 gap-6"></div>
                </div>
                
                <!-- TEMPLATES CONTAINER -->
                <div id="templatesContainer" class="bg-white rounded-lg shadow p-6 hidden">
                    <div class="flex justify-between items-center mb-6">
                        <h2 class="text-xl font-semibold text-slate-800">Gespeicherte Vorlagen</h2>
                        <!-- NEU: Button zum Anlegen -->
                        <button class="bg-primary text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-600 flex items-center gap-2 shadow-sm" onclick="openCreateTemplateModal()">
                            <span class="material-icons-outlined text-base">add</span> Neue Vorlage
                        </button>
                    </div>
                    <div class="overflow-x-auto custom-scrollbar"><table class="w-full min-w-[800px] text-sm text-left text-slate-text">
                        <thead class="text-xs text-gray-700 uppercase bg-gray-50">
                            <tr>
                                <th class="px-6 py-3 rounded-l-lg" scope="col">Artikel</th>
                                <th class="px-6 py-3" scope="col">Menge</th>
                                <th class="px-6 py-3" scope="col">Einheit</th>
                                <th class="px-6 py-3" scope="col">Lagerort</th>
                                <th class="px-6 py-3" scope="col">STD Nr.</th>
                                <th class="px-6 py-3" scope="col">Zulieferer</th>
                                <th class="px-6 py-3" scope="col">Link</th>
                                <th class="px-6 py-3 rounded-r-lg" scope="col">Aktionen</th>
                            </tr>
                        </thead>
                        <tbody id="templateTableBody"></tbody>
                    </table></div>
                </div>
                
                <div id="expiryContainer" class="bg-white rounded-lg shadow p-6 hidden"><div class="flex justify-between items-center mb-6"><h2 class="text-xl font-semibold text-slate-800">Bestand mit Verfallsdatum</h2></div><div class="overflow-x-auto custom-scrollbar"><table class="w-full min-w-[600px] text-sm text-left text-slate-text"><thead class="text-xs text-gray-700 uppercase bg-gray-50"><tr><th class="px-6 py-3 rounded-l-lg" scope="col">Artikel</th><th class="px-6 py-3" scope="col">Charge</th><th class="px-6 py-3" scope="col">Geliefert am</th><th class="px-6 py-3" scope="col">Verfällt am</th><th class="px-6 py-3 rounded-r-lg" scope="col"></th></tr></thead><tbody id="expiryTableBody"></tbody></table></div></div>
            </div>
        </div>
    </main>
</div>

<!-- MODALS -->

<!-- New Order Modal (Bestätigung/Read-Only aus Vorlage) -->
<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="newOrderModal"> 
    <div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="closeModal('newOrderModal')"></div> 
    <div class="modal-container bg-white w-11/12 md:max-w-lg mx-auto rounded-xl shadow-lg z-50"> 
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-border"><p class="text-2xl font-bold text-slate-800">Bestellung bestätigen</p><div class="modal-close cursor-pointer z-50" onclick="closeModal('newOrderModal')"><span class="material-icons-outlined text-gray-500 hover:text-gray-700">close</span></div></div> 
        <div class="flex-grow overflow-y-auto py-4 px-6 custom-scrollbar"> 
            <p class="text-sm text-slate-500 mb-4">Bitte bestätige die Bestellung für den ausgewählten Artikel.</p>
            <form id="newOrderForm" class="space-y-4"> 
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="itemName">Artikel</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-slate-600 focus:outline-none text-sm readonly-input" id="itemName" name="itemName" readonly required/></div> 
                <div class="grid grid-cols-2 gap-4">
                    <div><label class="block text-sm font-medium text-slate-text mb-1" for="quantity">Menge (Anzahl)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="quantity" name="quantity" type="number" required/></div>
                    <div><label class="block text-sm font-medium text-slate-text mb-1" for="packageSize">Einheit/Größe</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-slate-600 focus:outline-none text-sm readonly-input" id="packageSize" name="packageSize" readonly/></div>
                </div>
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="storageLocation">Lagerort</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-slate-600 focus:outline-none text-sm readonly-input" id="storageLocation" name="storageLocation" type="text" readonly/></div> 
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="stdOrderNr">STD Order-Nr.</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-slate-600 focus:outline-none text-sm readonly-input" id="stdOrderNr" name="stdOrderNr" type="text" readonly/></div> 
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="supplier">Zulieferer</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-slate-600 focus:outline-none text-sm readonly-input" id="supplier" name="supplier" type="text" readonly/></div> 
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="link">Link</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-slate-600 focus:outline-none text-sm readonly-input" id="link" name="link" type="url" readonly/></div> 
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="notes">Notizen</label><textarea class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="notes" name="notes" rows="3"></textarea></div>
                <div><label class="flex items-center space-x-2 text-sm font-medium text-slate-text"><input type="checkbox" id="isCritical" name="isCritical" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"/><span>Dies ist eine kritische Bestellung (Dringend!)</span></label></div> 
                <div><label class="block text-sm font-medium text-slate-text mb-1" for="requester">Angefordert von</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-sm readonly-input" id="requester" name="requester" type="text" readonly/></div> 
            </form> 
        </div> 
        <div class="flex-shrink-0 py-4 px-6 flex justify-end items-center border-t border-slate-border space-x-3"> 
            <button class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200" type="button" onclick="closeModal('newOrderModal')">Abbrechen</button> 
            <button class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700 flex items-center space-x-2" type="submit" form="newOrderForm"><span class="material-icons-outlined text-base">send</span><span>Anfrage senden</span></button> 
        </div> 
    </div> 
</div>

<!-- Edit Order Modal (Bleibt unverändert) -->
<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="editOrderModal"> <div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="closeModal('editOrderModal')"></div> <div class="modal-container bg-white w-11/12 md:max-w-lg mx-auto rounded-xl shadow-lg z-50"> <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-border"><div class="flex items-center gap-3"><p class="text-2xl font-bold text-slate-800" id="editModalTitle">Bestelldetails</p><button id="switchToEditBtn" onclick="switchToEditMode()" class="p-1.5 rounded-full hover:bg-slate-100 text-slate-500" title="Bearbeiten"><span class="material-icons-outlined text-lg">edit</span></button></div><div class="modal-close cursor-pointer z-50" onclick="closeModal('editOrderModal')"><span class="material-icons-outlined text-gray-500 hover:text-gray-700">close</span></div></div> <div class="flex-grow overflow-y-auto py-4 px-6 custom-scrollbar"> 
    <div id="viewOrderContent" class="space-y-4 text-sm"> 
        <div id="viewCriticalStatus" class="hidden p-3 rounded-md bg-red-50 border border-red-200"><p class="text-sm font-semibold text-red-700 flex items-center gap-2"><span class="material-icons-outlined">error</span>Kritische Priorität</p></div> 
        <div><label class="block text-xs font-medium text-slate-500">Artikel</label><p id="viewItemName" class="font-medium text-slate-800 text-base"></p></div> 
        <div class="grid grid-cols-2 gap-4">
            <div><label class="block text-xs font-medium text-slate-500">Menge</label><p id="viewQuantity" class="text-slate-700"></p></div>
            <div><label class="block text-xs font-medium text-slate-500">Einheit/Größe</label><p id="viewPackageSize" class="text-slate-700"></p></div> 
        </div>
        <div id="viewStorageLocationContainer" class="hidden"><label class="block text-xs font-medium text-slate-500">Lagerort</label><p id="viewStorageLocation" class="text-slate-700"></p></div> <div id="viewStdOrderNrContainer" class="hidden"><label class="block text-xs font-medium text-slate-500">STD Order-Nr.</label><p id="viewStdOrderNr" class="font-mono text-slate-700"></p></div> <div id="viewSupplierContainer" class="hidden"><label class="block text-xs font-medium text-slate-500">Zulieferer</label><p id="viewSupplier" class="text-slate-700"></p></div> <div id="viewLinkContainer" class="hidden"><label class="block text-xs font-medium text-slate-500">Link</label><a id="viewLink" href="#" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline break-all"></a></div> <div id="viewNotesContainer"><label class="block text-xs font-medium text-slate-500">Notizen</label><p id="viewNotes" class="text-slate-700 whitespace-pre-wrap bg-slate-50 p-2 rounded-md border"></p></div> <div id="viewInProgressDetails" class="hidden pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Bestelldaten</h4><div><label class="block text-xs font-medium text-slate-500">Bestellt am</label><p id="viewOrderedAt" class="text-slate-700"></p></div><div><label class="block text-xs font-medium text-slate-500">Voraussichtliche Lieferung</label><p id="viewEta" class="text-slate-700"></p></div><div><label class="block text-xs font-medium text-slate-500">Bestellt von</label><p id="viewOrderedBy" class="text-slate-700"></p></div><div id="viewBanfNrContainer" class="hidden"><label class="block text-xs font-medium text-slate-500">BANF Nr.</label><p id="viewBanfNr" class="font-mono text-slate-700"></p></div></div> <div id="viewCompletedDetails" class="hidden pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Lieferdaten</h4><div><label class="block text-xs font-medium text-slate-500">Geliefert am</label><p id="viewDeliveredAt" class="text-slate-700"></p></div><div id="viewBatchContainer"><label class="block text-xs font-medium text-slate-500">Chargennummer</label><p id="viewBatchNumber" class="text-slate-700"></p></div><div id="viewExpiryContainer"><label class="block text-xs font-medium text-slate-500">Verfallsdatum</label><p id="viewExpiryDate" class="text-slate-700"></p></div></div> <div id="viewDocumentationDetails" class="hidden pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Dokumentation</h4><div id="viewDocStatusContainer"><label class="block text-xs font-medium text-slate-500">Status</label><p id="viewDocStatus" class="flex items-center gap-2"></p></div><div id="viewDocFileContainer" class="hidden"><label class="block text-xs font-medium text-slate-500">Dokument</label><p id="viewDocFile" class="text-slate-700 font-mono"></p></div></div> <div class="pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Anforderungsdetails</h4><div><label class="block text-xs font-medium text-slate-500">Bestell-ID</label><p id="viewOrderId" class="font-mono text-slate-700"></p></div><div><label class="block text-xs font-medium text-slate-500">Angefordert von</label><p id="viewRequester" class="text-slate-700"></p></div><div><label class="block text-xs font-medium text-slate-500">Erstellt am</label><p id="viewCreatedAt" class="text-slate-700"></p></div></div> 
    </div> 
    
    <form id="editOrderForm" class="space-y-4 hidden"> 
        <input type="hidden" id="editOrderId"> 
        <div><label class="block text-sm font-medium text-slate-text mb-1" for="editItemName">Artikel</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editItemName" required/></div> 
        <div class="grid grid-cols-2 gap-4">
            <div><label class="block text-sm font-medium text-slate-text mb-1" for="editQuantity">Menge</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editQuantity" type="number" required/></div> 
            <div><label class="block text-sm font-medium text-slate-text mb-1" for="editPackageSize">Einheit/Größe</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editPackageSize" /></div>
        </div>

        <div><label class="block text-sm font-medium text-slate-text mb-1" for="editStorageLocation">Lagerort (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editStorageLocation" type="text"/></div> <div><label class="block text-sm font-medium text-slate-text mb-1" for="editStdOrderNr">STD Order-Nr. (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editStdOrderNr" type="text"/></div> <div><label class="block text-sm font-medium text-slate-text mb-1" for="editSupplier">Zulieferer (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editSupplier" type="text"/></div> <div><label class="block text-sm font-medium text-slate-text mb-1" for="editLink">Link (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editLink" type="url" placeholder="https://..."/></div> <div><label class="block text-sm font-medium text-slate-text mb-1" for="editNotes">Notizen</label><textarea class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editNotes" rows="3"></textarea></div> <div><label class="flex items-center space-x-2 text-sm font-medium text-slate-text"><input type="checkbox" id="editIsCritical" name="isCritical" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"/><span>Dies ist eine kritische Bestellung</span></label></div> <div id="editInProgressFields" class="hidden pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Bestelldaten</h4><div><label class="block text-sm font-medium text-slate-text mb-1" for="editOrderedAt">Bestellt am</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary" id="editOrderedAt" type="date"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editEta">Voraussichtliche Lieferung</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary" id="editEta" type="date"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editBanfNr">BANF Nr. (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary font-mono" id="editBanfNr" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editOrderedBy">Bestellt von</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-sm" id="editOrderedBy" readonly/></div></div> <div id="editCompletedFields" class="hidden pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Lieferdaten</h4><div><label class="block text-sm font-medium text-slate-text mb-1" for="editDeliveredAt">Geliefert am</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary" id="editDeliveredAt" type="date"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editBatchNumber">Chargennummer</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary" id="editBatchNumber" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editExpiryDate">Verfallsdatum</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-primary" id="editExpiryDate" type="date"/></div></div> <div id="editDocumentationFields" class="hidden pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Dokumentation</h4><div><label class="flex items-center space-x-2 text-sm font-medium text-slate-text"><input type="checkbox" id="editDocumentationChecked" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"/><span>Dokumentation geprüft</span></label></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editDocumentationFile">Neues Dokument hochladen (optional)</label><input id="editDocumentationFile" type="file" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-primary hover:file:bg-blue-100"/><p class="text-xs text-slate-500 mt-1 hidden">Aktuelle Datei: <span id="currentDocFile" class="font-mono"></span></p></div></div> <div class="pt-4 mt-4 border-t border-slate-200 space-y-4"><h4 class="text-base font-semibold text-slate-700">Anforderungsdetails</h4><div><label class="block text-sm font-medium text-slate-text mb-1" for="editRequester">Angefordert von</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg bg-gray-50 text-sm" id="editRequester" readonly/></div></div> <div class="mt-4 pt-4 border-t border-slate-200"><button type="button" id="saveEditAsTemplateBtn" class="w-full flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium text-primary bg-blue-50 rounded-lg hover:bg-blue-100"><span class="material-icons-outlined text-base">bookmark_add</span><span>Aktuelle Bearbeitung als Vorlage speichern</span></button></div> </form> </div> <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-t border-slate-border"> <button id="deleteOrderBtn" type="button" class="px-4 py-2 text-sm font-medium text-red-600 bg-red-100 rounded-lg hover:bg-red-200 flex items-center gap-1 hidden"><span class="material-icons-outlined text-base">delete_outline</span><span>Löschen</span></button> <div id="editButtons" class="space-x-3 hidden ml-auto"><button class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200" type="button" onclick="switchToViewMode()">Abbrechen</button><button class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700" type="submit" form="editOrderForm">Änderungen speichern</button></div> <div id="viewButtons" class="space-x-3 ml-auto"><button class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700" type="button" onclick="closeModal('editOrderModal')">Schließen</button></div> </div> </div> </div>

<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="templateModal">
    <div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="closeModal('templateModal')"></div>
    <div class="modal-container bg-white w-11/12 md:max-w-xl mx-auto rounded-xl shadow-lg z-50 flex flex-col h-[80vh]">
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-border">
            <p class="text-2xl font-bold text-slate-800">Bestellung auswählen</p>
            <div class="modal-close cursor-pointer z-50" onclick="closeModal('templateModal')"><span class="material-icons-outlined text-gray-500 hover:text-gray-700">close</span></div>
        </div>
        
        <!-- Suchleiste im Header des Modals -->
        <div class="flex-shrink-0 px-6 py-3 bg-slate-50 border-b border-slate-border">
            <div class="relative">
                <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">search</span>
                <input type="text" id="templateSearchInput" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary text-sm" placeholder="Artikel suchen..." oninput="renderTemplatesForSelection()">
            </div>
        </div>

        <div id="templateListContainer" class="flex-grow overflow-y-auto py-4 px-6 space-y-2 custom-scrollbar"></div>
        
        <div class="flex-shrink-0 py-4 px-6 flex justify-end items-center border-t border-slate-border">
            <button class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200" type="button" onclick="closeModal('templateModal')">Abbrechen</button>
        </div>
    </div>
</div>

<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="editTemplateModal"><div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="closeModal('editTemplateModal')"></div><div class="modal-container bg-white w-11/12 md:max-w-lg mx-auto rounded-xl shadow-lg z-50"><div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-border"><p class="text-2xl font-bold text-slate-800">Vorlage bearbeiten</p><div class="modal-close cursor-pointer z-50" onclick="closeModal('editTemplateModal')"><span class="material-icons-outlined text-gray-500 hover:text-gray-700">close</span></div></div><div class="flex-grow overflow-y-auto py-4 px-6 custom-scrollbar">
    <form id="editTemplateForm" class="space-y-4">
        <input type="hidden" id="editTemplateIndex">
        <div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateItemName">Artikel</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateItemName" required/></div>
        <div class="grid grid-cols-2 gap-4">
            <div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateQuantity">Menge</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateQuantity" type="number" required/></div>
            <div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplatePackageSize">Einheit/Größe</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplatePackageSize" /></div>
        </div>

        <div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateStorageLocation">Lagerort (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateStorageLocation" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateStdOrderNr">STD Order-Nr. (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateStdOrderNr" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateSupplier">Zulieferer (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateSupplier" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateLink">Link (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateLink" type="url" placeholder="https://..."/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="editTemplateNotes">Notizen (optional)</label><textarea class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="editTemplateNotes" rows="3"></textarea></div><div><label class="flex items-center space-x-2 text-sm font-medium text-slate-text"><input type="checkbox" id="editTemplateIsCritical" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"/><span>Als kritisch markieren</span></label></div></form></div><div class="flex-shrink-0 py-4 px-6 flex justify-end items-center border-t border-slate-border space-x-3"><button class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200" type="button" onclick="closeModal('editTemplateModal')">Abbrechen</button><button class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700" type="submit" form="editTemplateForm">Änderungen speichern</button></div></div></div>

<!-- NEU: Modal zum Erstellen einer Vorlage (leer) -->
<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="createTemplateModal"><div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="closeModal('createTemplateModal')"></div><div class="modal-container bg-white w-11/12 md:max-w-lg mx-auto rounded-xl shadow-lg z-50"><div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-border"><p class="text-2xl font-bold text-slate-800">Neue Vorlage erstellen</p><div class="modal-close cursor-pointer z-50" onclick="closeModal('createTemplateModal')"><span class="material-icons-outlined text-gray-500 hover:text-gray-700">close</span></div></div><div class="flex-grow overflow-y-auto py-4 px-6 custom-scrollbar">
    <form id="createTemplateForm" class="space-y-4">
        <div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateItemName">Artikel</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateItemName" required/></div>
        <div class="grid grid-cols-2 gap-4">
            <div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateQuantity">Menge</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateQuantity" type="number" required/></div>
            <div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplatePackageSize">Einheit/Größe</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplatePackageSize" /></div>
        </div>
        <div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateStorageLocation">Lagerort (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateStorageLocation" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateStdOrderNr">STD Order-Nr. (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateStdOrderNr" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateSupplier">Zulieferer (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateSupplier" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateLink">Link (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateLink" type="url" placeholder="https://..."/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="createTemplateNotes">Notizen (optional)</label><textarea class="w-full px-3 py-2 border border-slate-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary text-sm" id="createTemplateNotes" rows="3"></textarea></div><div><label class="flex items-center space-x-2 text-sm font-medium text-slate-text"><input type="checkbox" id="createTemplateIsCritical" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"/><span>Als kritisch markieren</span></label></div></form></div><div class="flex-shrink-0 py-4 px-6 flex justify-end items-center border-t border-slate-border space-x-3"><button class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200" type="button" onclick="closeModal('createTemplateModal')">Abbrechen</button><button class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700" type="submit" form="createTemplateForm">Vorlage erstellen</button></div></div></div>

<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="inProgressModal"><div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="cancelStatusChange('inProgress')"></div><div class="modal-container bg-white w-11/12 md:max-w-md mx-auto rounded-xl shadow-lg z-50"><div class="p-6"><h3 class="text-xl font-bold text-slate-800">Bestellung wird bearbeitet</h3><p class="text-sm text-slate-500 mt-1">Bitte ergänze die Bestelldaten für <strong id="inProgressItemName"></strong>.</p><form id="inProgressForm" class="mt-6 space-y-4"><input type="hidden" id="inProgressOrderId"><div><label class="block text-sm font-medium text-slate-text mb-1" for="orderedAt">Bestelldatum*</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg" id="orderedAt" type="date" required/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="eta">Voraussichtliche Lieferung</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg" id="eta" type="date"></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="banfNr">BANF Nr. (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg font-mono" id="banfNr" type="text"/></div><div class="flex justify-end items-center pt-4 space-x-3"><button type="button" onclick="cancelStatusChange('inProgress')" class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200">Abbrechen</button><button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700">Speichern</button></div></form></div></div></div>
<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="completedModal"><div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50" onclick="cancelStatusChange('completed')"></div><div class="modal-container bg-white w-11/12 md:max-w-md mx-auto rounded-xl shadow-lg z-50"><div class="p-6"><h3 class="text-xl font-bold text-slate-800">Bestellung abschließen</h3><p class="text-sm text-slate-500 mt-1">Bitte ergänze die Lieferdaten für <strong id="completedItemName"></strong>.</p><form id="completedForm" class="mt-6 space-y-4"><input type="hidden" id="completedOrderId"><div><label class="block text-sm font-medium text-slate-text mb-1" for="deliveredAt">Lieferdatum*</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg" id="deliveredAt" type="date" required/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="batchNumber">Chargennummer (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg" id="batchNumber" type="text"/></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="expiryDate">Verfallsdatum (optional)</label><input class="w-full px-3 py-2 border border-slate-border rounded-lg" id="expiryDate" type="date"/></div><hr/><div><label class="flex items-center space-x-2 text-sm font-medium text-slate-text"><input type="checkbox" id="documentationChecked" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"/><span>Dokumentation geprüft</span></label></div><div><label class="block text-sm font-medium text-slate-text mb-1" for="documentationFile">Dokumentation hochladen (optional)</label><input id="documentationFile" type="file" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-primary hover:file:bg-blue-100"/></div><div class="flex justify-end items-center pt-4 space-x-3"><button type="button" onclick="cancelStatusChange('completed')" class="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200">Abbrechen</button><button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700">Abschließen</button></div></form></div></div></div>

<div id="currentUser" data-name="{{ current_user_display_name }}" class="hidden"></div>
<script id="orders-data" type="application/json">{{ orders_json | safe }}</script>
<script id="templates-data" type="application/json">{{ templates_json | safe }}</script>

<script>
    // --- GLOBALE FUNKTIONEN (Außerhalb DOMContentLoaded) ---
    let orders = [];
    let templates = [];
    let chartInstances = {};
    const TABLE_ROW_LIMIT = 20;
    let isTableExpanded = false;
    let pendingStatusChange = null;

    // Diese globalen Variablen werden erst in DOMContentLoaded gefüllt
    let dom = {};
    let canManageOrders = false;
    let canViewAnalysis = false;
    let canManageTemplates = false;
    
    // Hilfsfunktion: Templates rendern
    function renderTemplatesForSelection() { 
        const container = document.getElementById('templateListContainer');
        const searchTerm = document.getElementById('templateSearchInput').value.toLowerCase();
        container.innerHTML = ''; 
        
        const filteredTemplates = templates.filter(t => t.name.toLowerCase().includes(searchTerm) || (t.notes && t.notes.toLowerCase().includes(searchTerm)));

        if (filteredTemplates.length === 0) { 
            container.innerHTML = '<p class="text-slate-500 text-center py-4">Keine passenden Vorlagen gefunden.</p>'; 
            return; 
        } 
        
        filteredTemplates.forEach((template) => { 
            const originalIndex = templates.indexOf(template);
            
            const templateEl = document.createElement('div'); 
            templateEl.className = 'p-4 border rounded-lg hover:bg-blue-50 cursor-pointer transition-colors bg-white shadow-sm mb-2'; 
            let detailsHtml = `<p class="text-sm text-slate-500 mt-1">Menge: <strong>${template.quantity}</strong> | ${template.packageSize || '-'}</p>`; 
            
            let metaInfo = [];
            if(template.storageLocation) metaInfo.push(template.storageLocation);
            if(template.stdOrderNr) metaInfo.push(template.stdOrderNr);
            if(template.supplier) metaInfo.push(template.supplier);
            
            if(metaInfo.length > 0) {
                detailsHtml += `<p class="text-xs text-slate-400 mt-2 truncate">${metaInfo.join(' • ')}</p>`;
            }
            
            templateEl.innerHTML = `<div class="flex justify-between items-start"><p class="font-bold text-slate-800">${template.name}</p>${template.isCritical ? '<span class="text-xs text-red-500 font-bold border border-red-200 bg-red-50 px-2 py-0.5 rounded-full">Kritisch</span>' : ''}</div>${detailsHtml}`; 
            templateEl.onclick = () => window.applyTemplate(originalIndex); 
            container.appendChild(templateEl); 
        }); 
    }

    // Modal Funktionen Global
    window.openModal = function(modalId) { 
        const modal = document.getElementById(modalId); 
        if(modalId === 'newOrderModal') { 
            const userEl = document.getElementById('currentUser');
            document.getElementById('requester').value = userEl ? userEl.dataset.name : 'User'; 
        } 
        if (modalId === 'templateModal') {
            document.getElementById('templateSearchInput').value = '';
            renderTemplatesForSelection(); 
        }
        if (modalId === 'createTemplateModal') {
            document.getElementById('createTemplateForm').reset();
        }
        modal.classList.remove('opacity-0', 'pointer-events-none'); 
        document.body.classList.add('modal-active'); 
    };

    window.closeModal = function(modalId) { 
        const modal = document.getElementById(modalId); 
        modal.classList.add('opacity-0', 'pointer-events-none'); 
        document.body.classList.remove('modal-active'); 
        if (modalId === 'editOrderModal') setTimeout(()=>window.switchToViewMode(true), 300); 
    };
    
    // Für oninput im HTML
    window.renderTemplatesForSelection = renderTemplatesForSelection;
    
    window.openCreateTemplateModal = function() {
        openModal('createTemplateModal');
    }

    window.applyTemplate = function(templateIndex) { 
        const template = templates[templateIndex]; 
        document.getElementById('itemName').value = template.name; 
        document.getElementById('quantity').value = template.quantity; 
        document.getElementById('packageSize').value = template.packageSize || ''; 
        document.getElementById('notes').value = template.notes || ''; 
        document.getElementById('stdOrderNr').value = template.stdOrderNr || ''; 
        document.getElementById('supplier').value = template.supplier || ''; 
        document.getElementById('link').value = template.link || ''; 
        document.getElementById('storageLocation').value = template.storageLocation || ''; 
        document.getElementById('isCritical').checked = template.isCritical; 
        
        closeModal('templateModal');
        openModal('newOrderModal');
    }
    window.saveTemplateFromData = async function(data) { 
        const { name, quantity, packageSize, notes, isCritical, stdOrderNr, supplier, link, storageLocation } = data; 
        if (!name || !quantity) { alert("Artikel und Menge sind erforderlich."); return; } 
        const alreadyExists = templates.some(t => t.name.toLowerCase() === name.toLowerCase() && t.quantity === quantity); 
        if (alreadyExists) { if (!confirm('Eine sehr ähnliche Vorlage existiert bereits. Trotzdem speichern?')) return; } 
        templates.push({ name, quantity, packageSize, notes, isCritical, stdOrderNr, supplier, link, storageLocation }); 
        await saveDataToServer(); 
        alert(`Vorlage "${name}" wurde gespeichert.`); 
        // Falls wir gerade im Template-Tab sind, Tabelle neu laden
        if (document.querySelector('#templatesContainer:not(.hidden)')) { renderTemplateTable(); } 
    }

    window.openEditTemplateModal = function(index) { 
        const template = templates[index]; 
        if (!template) return; 
        document.getElementById('editTemplateIndex').value = index; 
        document.getElementById('editTemplateItemName').value = template.name; 
        document.getElementById('editTemplateQuantity').value = template.quantity; 
        document.getElementById('editTemplatePackageSize').value = template.packageSize || ''; 
        document.getElementById('editTemplateNotes').value = template.notes || ''; 
        document.getElementById('editTemplateIsCritical').checked = template.isCritical; 
        document.getElementById('editTemplateStdOrderNr').value = template.stdOrderNr || ''; 
        document.getElementById('editTemplateSupplier').value = template.supplier || ''; 
        document.getElementById('editTemplateLink').value = template.link || ''; 
        document.getElementById('editTemplateStorageLocation').value = template.storageLocation || ''; 
        openModal('editTemplateModal'); 
    }

    window.deleteTemplate = async function(index) { 
        if (confirm(`Möchten Sie die Vorlage "${templates[index].name}" wirklich löschen?`)) { 
            templates.splice(index, 1); 
            await saveDataToServer(); 
            renderTemplateTable(); 
        } 
    }
    document.addEventListener('DOMContentLoaded', () => {
        // --- Sidebar ---
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) { 
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined'); 
            const applyState = (isCollapsed) => { if (isCollapsed) { sidebar.classList.add('is-collapsed'); toggleBtnIcon.textContent = 'menu'; } else { sidebar.classList.remove('is-collapsed'); toggleBtnIcon.textContent = 'chevron_left'; } }; 
            sidebarToggleBtn.addEventListener('click', () => { const isNowCollapsed = !sidebar.classList.contains('is-collapsed'); localStorage.setItem('sidebarCollapsed', isNowCollapsed); applyState(isNowCollapsed); }); 
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true'; applyState(savedState); 
        }

        // --- Init Data ---
        const userRoles = {{ session.get('roles', []) | tojson }};
        canManageOrders = userRoles.includes('ADMIN') || userRoles.includes('CONSUMABLEMANAGER');
        canViewAnalysis = userRoles.includes('ADMIN') || userRoles.includes('CONSUMABLEMANAGER') || userRoles.includes('PLANNER');
        canManageTemplates = userRoles.includes('ADMIN') || userRoles.includes('CONSUMABLEMANAGER');

        orders = JSON.parse(document.getElementById('orders-data').textContent);
        templates = JSON.parse(document.getElementById('templates-data').textContent);
        orders.forEach(order => { order.createdAt = new Date(order.createdAt); });

        // --- DOM Elements ---
        dom = { kanbanContainerParent: document.getElementById('kanban-container-parent'), tableBody: document.getElementById('orderHistoryTableBody'), newOrderForm: document.getElementById('newOrderForm'), editOrderForm: document.getElementById('editOrderForm'), kpis: { requested: document.getElementById('kpi-requested'), inprogress: document.getElementById('kpi-inprogress'), critical: document.getElementById('kpi-critical'), expiry: document.getElementById('kpi-expiry'), expiryTile: document.getElementById('kpi-expiry-tile'), expiryIconContainer: document.getElementById('kpi-expiry-icon-container') }, editModal: { title: document.getElementById('editModalTitle'), viewContent: document.getElementById('viewOrderContent'), editForm: document.getElementById('editOrderForm'), switchToEditBtn: document.getElementById('switchToEditBtn'), deleteBtn: document.getElementById('deleteOrderBtn'), editButtons: document.getElementById('editButtons'), viewButtons: document.getElementById('viewButtons'), viewOrderId: document.getElementById('viewOrderId'), viewItemName: document.getElementById('viewItemName'), viewQuantity: document.getElementById('viewQuantity'), viewPackageSize: document.getElementById('viewPackageSize'), viewRequester: document.getElementById('viewRequester'), viewCreatedAt: document.getElementById('viewCreatedAt'), viewNotesContainer: document.getElementById('viewNotesContainer'), viewNotes: document.getElementById('viewNotes'), viewCriticalStatus: document.getElementById('viewCriticalStatus'), viewInProgressDetails: document.getElementById('viewInProgressDetails'), viewOrderedAt: document.getElementById('viewOrderedAt'), viewEta: document.getElementById('viewEta'), viewOrderedBy: document.getElementById('viewOrderedBy'), viewBanfNrContainer: document.getElementById('viewBanfNrContainer'), viewBanfNr: document.getElementById('viewBanfNr'), viewCompletedDetails: document.getElementById('viewCompletedDetails'), viewDeliveredAt: document.getElementById('viewDeliveredAt'), viewBatchContainer: document.getElementById('viewBatchContainer'), viewBatchNumber: document.getElementById('viewBatchNumber'), viewExpiryContainer: document.getElementById('viewExpiryContainer'), viewExpiryDate: document.getElementById('viewExpiryDate'), viewStdOrderNrContainer: document.getElementById('viewStdOrderNrContainer'), viewStdOrderNr: document.getElementById('viewStdOrderNr'), viewSupplierContainer: document.getElementById('viewSupplierContainer'), viewSupplier: document.getElementById('viewSupplier'), viewLinkContainer: document.getElementById('viewLinkContainer'), viewLink: document.getElementById('viewLink'), viewStorageLocationContainer: document.getElementById('viewStorageLocationContainer'), viewStorageLocation: document.getElementById('viewStorageLocation'), viewDocumentationDetails: document.getElementById('viewDocumentationDetails'), viewDocStatusContainer: document.getElementById('viewDocStatusContainer'), viewDocStatus: document.getElementById('viewDocStatus'), viewDocFileContainer: document.getElementById('viewDocFileContainer'), viewDocFile: document.getElementById('viewDocFile'), editDocumentationFields: document.getElementById('editDocumentationFields') }, tableSearchInput: document.getElementById('tableSearchInput'), tableFooter: document.getElementById('table-footer'), viewTabs: document.getElementById('viewTabs'), tableContainer: document.getElementById('tableContainer'), analyseContainer: document.getElementById('analyseContainer'), analyseChartsContainer: document.getElementById('analyseChartsContainer'), templateListContainer: document.getElementById('templateListContainer'), templatesContainer: document.getElementById('templatesContainer'), templateTableBody: document.getElementById('templateTableBody'), editTemplateForm: document.getElementById('editTemplateForm'), saveNewAsTemplateBtn: document.getElementById('saveNewAsTemplateBtn'), saveEditAsTemplateBtn: document.getElementById('saveEditAsTemplateBtn'), expiryContainer: document.getElementById('expiryContainer'), expiryTableBody: document.getElementById('expiryTableBody') };
        
        const statusStyles = { requested:  'bg-slate-100 text-slate-700 border-slate-300', inprogress: 'bg-blue-100 text-blue-700 border-blue-300', completed:  'bg-green-100 text-green-700 border-green-300' };

        // --- Core Functions ---
        async function saveDataToServer() {
            try {
                await fetch('/api/order_system/save_data', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ orders: orders, templates: templates })
                });
            } catch (error) {
                console.error("Fehler beim Speichern der Daten:", error);
                alert("Verbindung zum Server verloren. Änderungen konnten nicht gespeichert werden.");
            }
        }

        function renderAll() { renderKanban(); renderTable(); updateKPIs(); }
        
        function renderKanban() { 
            const groupedOrders = { requested: [], inprogress: [], completed: [] }; 
            orders.sort((a, b) => b.createdAt - a.createdAt).forEach(order => { if (groupedOrders[order.status]) { groupedOrders[order.status].push(order); } }); 
            Object.keys(groupedOrders).forEach(status => { 
                const columnEl = dom.kanbanContainerParent.querySelector(`.kanban-column[data-status="${status}"]`); 
                const cardContainer = columnEl.querySelector('.kanban-card-container'); 
                cardContainer.innerHTML = ''; 
                const ordersForColumn = groupedOrders[status]; 
                ordersForColumn.forEach(order => { 
                    const card = document.createElement('div'); card.className = 'kanban-card'; 
                    if (order.isCritical && order.status !== 'completed') card.classList.add('is-critical'); 
                    card.draggable = true; card.dataset.orderId = order.id; 
                    let extraInfo = ''; 
                    if(order.status === 'inprogress' && order.orderedAt && order.eta) { const etaDate = new Date(order.eta).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); extraInfo = `<p class="text-xs text-blue-600 pointer-events-none mt-1">Bestellt von ${order.orderedBy}, ETA: ${etaDate}</p>`; }
                    if(order.status === 'completed' && order.deliveredAt) { const deliveredDate = new Date(order.deliveredAt).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); extraInfo = `<p class="text-xs text-green-700 pointer-events-none mt-1">Geliefert am ${deliveredDate}</p>`; }
                    const banfLine = order.banfNr ? `<p class="text-xs text-slate-400 pointer-events-none mt-2">BANF: ${order.banfNr}</p>` : '';
                    card.innerHTML = `${order.isCritical && order.status !== 'completed' ? '<span class="critical-icon material-icons-outlined">error</span>' : ''}<p class="font-medium text-sm pointer-events-none text-slate-800">${order.name}</p><p class="text-xs text-gray-500 pointer-events-none mt-1">Menge: ${order.quantity} | Einheit: ${order.packageSize || '-'}</p>${banfLine}${extraInfo}`; 
                    card.addEventListener('dblclick', () => openOrderModal(order)); 
                    addDragAndDropEvents(card); 
                    cardContainer.appendChild(card); 
                }); 
                dom.kanbanContainerParent.querySelectorAll('.kanban-card-container').forEach(updateScrollFadeEffect); 
            }); 
        }

        function renderTable() {
            const searchTerm = dom.tableSearchInput.value.toLowerCase();
            const filteredOrders = orders.filter(order => { const searchFields = [ order.name, order.quantity, order.packageSize, order.requester, String(order.id), order.storageLocation, order.stdOrderNr, order.supplier, order.notes, order.banfNr ]; return searchFields.some(field => (field || '').toLowerCase().includes(searchTerm)); });
            const sortedOrders = filteredOrders.sort((a, b) => b.createdAt - a.createdAt);
            const ordersToDisplay = isTableExpanded ? sortedOrders : sortedOrders.slice(0, TABLE_ROW_LIMIT);
            dom.tableBody.innerHTML = '';
            ordersToDisplay.forEach(order => {
                const row = document.createElement('tr'); row.className = 'bg-white border-b border-slate-200'; if(order.isCritical && order.status !== 'completed') row.classList.add('bg-red-50'); row.dataset.orderId = order.id;
                let deliveryDateInfo = '-'; if (order.status === 'completed' && order.deliveredAt) { deliveryDateInfo = new Date(order.deliveredAt).toLocaleDateString('de-DE'); } else if (order.status === 'inprogress' && order.eta) { deliveryDateInfo = `ca. ${new Date(order.eta).toLocaleDateString('de-DE')}`; }
                let docHtml = '-'; if (order.status === 'completed') { docHtml = '<div class="flex items-center space-x-2">'; if (order.documentationChecked) { docHtml += '<span class="material-icons-outlined text-green-600 text-base" title="Dokumentation geprüft">check_circle</span>'; } else { docHtml += '<span class="material-icons-outlined text-slate-400 text-base" title="Dokumentation nicht geprüft">cancel</span>'; } if (order.documentationFile) { docHtml += `<span class="material-icons-outlined text-slate-500 text-base" title="Dokument vorhanden: ${order.documentationFile}">attachment</span>`; } docHtml += '</div>'; }
                const statusClass = statusStyles[order.status] || statusStyles.requested;
                const disabledAttr = canManageOrders ? '' : 'disabled';
                const statusSelectHtml = `<select class="status-select text-xs font-medium rounded-full border px-3 py-1 appearance-none focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary ${statusClass}" data-order-id="${order.id}" ${disabledAttr}><option value="requested" ${order.status === 'requested' ? 'selected' : ''}>Bedarf</option><option value="inprogress" ${order.status === 'inprogress' ? 'selected' : ''}>In Bestellung</option><option value="completed" ${order.status === 'completed' ? 'selected' : ''}>Abgeschlossen</option></select>`;
                row.innerHTML = `<td class="px-6 py-4 text-slate-500 font-mono">${order.id}</td><td class="px-6 py-4 font-medium text-slate-800">${order.isCritical && order.status !== 'completed' ? '<span class="material-icons-outlined text-red-500 text-base mr-1" title="Kritisch">error</span>' : ''}${order.name}</td><td class="px-6 py-4 text-slate-600">${order.quantity}</td><td class="px-6 py-4 text-slate-600">${order.packageSize || '-'}</td><td class="px-6 py-4 text-slate-600">${order.requester}</td><td class="px-6 py-4 text-slate-600">${new Date(order.createdAt).toLocaleDateString('de-DE')}</td><td class="px-6 py-4">${statusSelectHtml}</td><td class="px-6 py-4 text-slate-600">${order.orderedAt ? new Date(order.orderedAt).toLocaleDateString('de-DE') : '-'}</td><td class="px-6 py-4 text-slate-600 font-mono">${order.banfNr || '-'}</td><td class="px-6 py-4 text-slate-600">${deliveryDateInfo}</td><td class="px-6 py-4">${docHtml}</td><td class="px-6 py-4 text-right"><button class="toggle-details-btn text-primary hover:underline text-sm font-medium" data-order-id="${order.id}">Details</button></td>`;
                dom.tableBody.appendChild(row);

                const detailRow = document.createElement('tr'); detailRow.className = 'hidden detail-row'; detailRow.dataset.detailForRow = order.id; let linkHtml = order.link ? `<a href="${order.link}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline break-all">${order.link}</a>` : '-'; detailRow.innerHTML = `<td colspan="12" class="p-4 bg-slate-50"><div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm"><div class="space-y-2"><p><strong class="font-medium text-slate-600">Lagerort:</strong> ${order.storageLocation || '-'}</p><p><strong class="font-medium text-slate-600">STD Order-Nr.:</strong> <span class="font-mono">${order.stdOrderNr || '-'}</span></p></div><div class="space-y-2"><p><strong class="font-medium text-slate-600">Zulieferer:</strong> ${order.supplier || '-'}</p><p><strong class="font-medium text-slate-600">Link:</strong> ${linkHtml}</p></div><div class="space-y-2"><p><strong class="font-medium text-slate-600">Notizen:</strong></p><p class="text-slate-700 whitespace-pre-wrap">${order.notes || '-'}</p></div></div><div class="mt-4 pt-4 border-t border-slate-200 text-right"><button class="open-modal-btn px-4 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-blue-700" data-order-id="${order.id}">Vollständige Details / Bearbeiten</button></div></td>`;
                dom.tableBody.appendChild(detailRow);
            });

            dom.tableFooter.innerHTML = ''; if (sortedOrders.length > TABLE_ROW_LIMIT) { const buttonText = isTableExpanded ? 'Weniger anzeigen' : `Alle ${sortedOrders.length} anzeigen`; dom.tableFooter.innerHTML = `<button id="toggle-table-expansion" class="text-sm font-medium text-primary hover:underline">${buttonText}</button>`; }
        }

        function updateKPIs() { dom.kpis.requested.textContent = orders.filter(o => o.status === 'requested').length; dom.kpis.inprogress.textContent = orders.filter(o => o.status === 'inprogress').length; dom.kpis.critical.textContent = orders.filter(o => o.isCritical && o.status !== 'completed').length; const today = new Date(); today.setHours(0, 0, 0, 0); const expiryOrders = orders.filter(o => o.status === 'completed' && o.expiryDate); dom.kpis.expiry.textContent = expiryOrders.length; const hasExpired = expiryOrders.some(o => new Date(o.expiryDate) < today); if(hasExpired) { dom.kpis.expiryTile.classList.add('bg-red-50'); dom.kpis.expiryIconContainer.classList.remove('bg-orange-100'); dom.kpis.expiryIconContainer.classList.add('bg-red-100'); dom.kpis.expiryIconContainer.querySelector('span').classList.remove('text-orange-500'); dom.kpis.expiryIconContainer.querySelector('span').classList.add('text-red-500'); } else { dom.kpis.expiryTile.classList.remove('bg-red-50'); dom.kpis.expiryIconContainer.classList.add('bg-orange-100'); dom.kpis.expiryIconContainer.classList.remove('bg-red-100'); dom.kpis.expiryIconContainer.querySelector('span').classList.add('text-orange-500'); dom.kpis.expiryIconContainer.querySelector('span').classList.remove('text-red-500'); } }
        
        function renderTemplateTable() { dom.templateTableBody.innerHTML = ''; if(templates.length === 0) { dom.templateTableBody.innerHTML = '<tr><td colspan="8" class="text-center text-slate-500 py-8">Keine Vorlagen vorhanden.</td></tr>'; return; } templates.forEach((template, index) => { const row = document.createElement('tr'); row.className = 'bg-white border-b border-slate-200'; const linkHtml = template.link ? `<a href="${template.link}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">Link <span class="material-icons-outlined text-xs">open_in_new</span></a>` : '-'; 
            let actionsHtml = '-';
            if (canManageTemplates) {
                actionsHtml = `<button data-index="${index}" class="edit-template-btn p-1.5 rounded-full hover:bg-slate-100 text-slate-500" title="Bearbeiten"><span class="material-icons-outlined text-lg">edit</span></button>
                               <button data-index="${index}" class="delete-template-btn p-1.5 rounded-full hover:bg-red-100 text-red-500" title="Löschen"><span class="material-icons-outlined text-lg">delete</span></button>`;
            }
            row.innerHTML = ` <td class="px-6 py-4 font-medium text-slate-800">${template.name}</td> <td class="px-6 py-4 text-slate-600">${template.quantity}</td> <td class="px-6 py-4 text-slate-600">${template.packageSize || '-'}</td> <td class="px-6 py-4 text-slate-600">${template.storageLocation || '-'}</td> <td class="px-6 py-4 text-slate-600 font-mono">${template.stdOrderNr || '-'}</td> <td class="px-6 py-4 text-slate-600">${template.supplier || '-'}</td> <td class="px-6 py-4 text-slate-600">${linkHtml}</td> <td class="px-6 py-4 text-right space-x-2">${actionsHtml}</td> `; dom.templateTableBody.appendChild(row); }); 
        }
        function renderExpiryTable() { dom.expiryTableBody.innerHTML = ''; const today = new Date(); today.setHours(0, 0, 0, 0); const oneMonthFromNow = new Date(today); oneMonthFromNow.setMonth(oneMonthFromNow.getMonth() + 1); const expiryOrders = orders.filter(o => o.status === 'completed' && o.expiryDate); if (expiryOrders.length === 0) { dom.expiryTableBody.innerHTML = '<tr><td colspan="5" class="text-center text-slate-500 py-8">Keine Artikel mit Verfallsdatum im Bestand.</td></tr>'; return; } expiryOrders.sort((a, b) => new Date(a.expiryDate) - new Date(b.expiryDate)); expiryOrders.forEach(order => { const row = document.createElement('tr'); row.className = 'bg-white border-b border-slate-200 hover:bg-slate-50 cursor-pointer'; row.dataset.orderId = order.id; const expiryDate = new Date(order.expiryDate); const isExpired = expiryDate < today; const isExpiringSoon = expiryDate < oneMonthFromNow && !isExpired; let expiryClass = 'text-slate-600'; let expiryStatusText = ''; if (isExpired) { row.classList.add('bg-red-50'); expiryClass = 'text-red-600 font-bold'; expiryStatusText = '(Abgelaufen)'; } else if (isExpiringSoon) { row.classList.add('bg-orange-50'); expiryClass = 'text-orange-600 font-semibold'; expiryStatusText = '(Läuft bald ab)'; } row.innerHTML = `<td class="px-6 py-4 font-medium ${isExpired ? 'text-red-800' : ''}">${order.name}</td><td class="px-6 py-4 text-slate-600 font-mono">${order.batchNumber || '-'}</td><td class="px-6 py-4 text-slate-600">${new Date(order.deliveredAt).toLocaleDateString('de-DE')}</td><td class="px-6 py-4 ${expiryClass}">${expiryDate.toLocaleDateString('de-DE')} ${expiryStatusText}</td><td class="px-6 py-4 text-right"><button class="open-modal-btn text-primary hover:underline text-sm font-medium" data-order-id="${order.id}">Details</button></td>`; dom.expiryTableBody.appendChild(row); }); }

        // --- Render Analysis (bleibt gleich wie zuvor gepostet) ---
        function renderAnalysis() {
            dom.analyseChartsContainer.innerHTML = '';
            Object.values(chartInstances).forEach(chart => chart.destroy());
            const itemCounts = orders.reduce((acc, order) => { acc[order.name] = (acc[order.name] || 0) + 1; return acc; }, {});
            const sortedItems = Object.entries(itemCounts).sort(([,a],[,b]) => b-a).slice(0, 10);
            createChartCard('Häufigste Bestellungen (Top 10)', 'mostOrderedChart', 'doughnut', sortedItems.map(item => item[0]), { 'Anzahl': sortedItems.map(item => item[1]) }, { legend: true });
            const deliveryTimes = {};
            orders.filter(o => o.status === 'completed' && o.createdAt && o.deliveredAt).forEach(o => { const duration = (new Date(o.deliveredAt) - new Date(o.createdAt)) / (1000 * 60 * 60 * 24); if (!deliveryTimes[o.name]) deliveryTimes[o.name] = []; deliveryTimes[o.name].push(duration); });
            const avgDeliveryLabels = Object.keys(deliveryTimes);
            const avgDeliveryData = avgDeliveryLabels.map(name => (deliveryTimes[name].reduce((a, b) => a + b, 0) / deliveryTimes[name].length).toFixed(1));
            createChartCard('Durchschnittliche Lieferzeit', 'deliveryTimeChart', 'bar', avgDeliveryLabels, { 'Tage': avgDeliveryData }, { tooltipLabel: "Tage" });
            const createdMonthly = {};
            const completedMonthly = {};
            orders.forEach(order => { const createdMonth = new Date(order.createdAt).toISOString().slice(0, 7); createdMonthly[createdMonth] = (createdMonthly[createdMonth] || 0) + 1; if (order.status === 'completed' && order.deliveredAt) { const completedMonth = new Date(order.deliveredAt).toISOString().slice(0, 7); completedMonthly[completedMonth] = (completedMonthly[completedMonth] || 0) + 1; } });
            const allMonths = [...new Set([...Object.keys(createdMonthly), ...Object.keys(completedMonthly)])].sort();
            const monthlyLabels = allMonths.map(month => new Date(month + '-02').toLocaleString('de-DE', { month: 'short', year: '2-digit' }));
            createChartCard('Bestellaufkommen vs. Lieferungen', 'monthlyOrdersChart', 'line', monthlyLabels, { 'Neue Anforderungen': allMonths.map(month => createdMonthly[month] || 0), 'Abgeschlossene Lieferungen': allMonths.map(month => completedMonthly[month] || 0) }, { fullWidth: true, legend: true });
        }

        function createChartCard(title, canvasId, type, labels, datasets, options = {}) {
            const card = document.createElement('div');
            let cardClass = 'bg-white rounded-lg shadow-md p-6 border border-slate-200';
            if(options.fullWidth) cardClass += ' lg:col-span-3';
            else if(type === 'doughnut') cardClass += ' lg:col-span-1';
            else cardClass += ' lg:col-span-2';
            card.className = cardClass;
            const colorPalette = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#64748b', '#14b8a6', '#f97316', '#0ea5e9'];
            let legendHtml = '';
            if (options.legend) {
                legendHtml = '<div class="mt-4 pt-4 border-t border-slate-200"><div class="grid grid-cols-2 gap-3 text-xs">';
                if(type === 'doughnut') {
                    labels.forEach((label, index) => { legendHtml += `<div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full" style="background-color: ${colorPalette[index % colorPalette.length]}"></div><span class="truncate">${label}: <strong>${Object.values(datasets)[0][index]}</strong></span></div>`; });
                } else {
                    Object.keys(datasets).forEach((datasetLabel, index) => { legendHtml += `<div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full" style="background-color: ${colorPalette[index % colorPalette.length]}"></div><span class="truncate">${datasetLabel}</span></div>`; });
                }
                legendHtml += '</div></div>';
            }
            card.innerHTML = `<div class="mb-4"><h3 class="text-lg font-semibold text-slate-900">${title}</h3></div><div class="relative ${type === 'doughnut' ? 'h-64' : 'h-80'}"><canvas id="${canvasId}"></canvas></div>${legendHtml}`;
            dom.analyseChartsContainer.appendChild(card);
            setTimeout(() => {
                const ctx = document.getElementById(canvasId);
                if (ctx) {
                    const chartDatasets = Object.keys(datasets).map((label, index) => ({
                        label: label,
                        data: datasets[label],
                        backgroundColor: type === 'line' ? 'rgba(0,0,0,0)' : type === 'doughnut' ? colorPalette : colorPalette.map(c => c + 'B3'),
                        borderColor: type === 'doughnut' ? '#fff' : colorPalette[index % colorPalette.length],
                        borderWidth: type === 'doughnut' ? 2 : 2,
                        tension: 0.2,
                        fill: type === 'line'
                    }));
                    chartInstances[canvasId] = new Chart(ctx, {
                        type: type, data: { labels: labels, datasets: chartDatasets },
                        options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: (type === 'doughnut') ? {} : { y: { beginAtZero: true } }, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { let label = context.dataset.label || context.label || ''; if (label) { label += ': '; } const value = context.parsed.y || context.parsed; label += value; if (options.tooltipLabel) { label += ` ${options.tooltipLabel}`; } return label; } } } } }
                    });
                }
            }, 50);
        }

        function updateScrollFadeEffect(container) {
            const isScrollable = container.scrollHeight > container.clientHeight;
            const isAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 1;
            container.classList.toggle('scroll-fade', isScrollable && !isAtBottom);
        }

        dom.tableSearchInput.addEventListener('input', renderTable);
        dom.tableFooter.addEventListener('click', (e) => { if (e.target.id === 'toggle-table-expansion') { isTableExpanded = !isTableExpanded; renderTable(); } });
        
        dom.viewTabs.addEventListener('click', (e) => { 
            const button = e.target.closest('button'); 
            if (!button) return; 
            const view = button.dataset.view; 
            
            if (view === 'templates' && !canManageTemplates) return;
            if (view === 'analyse' && !canViewAnalysis) return;

            dom.viewTabs.querySelectorAll('button').forEach(btn => { 
                btn.classList.remove('border-primary', 'text-primary'); 
                btn.classList.add('border-transparent', 'text-slate-500', 'hover:border-slate-300', 'hover:text-slate-700'); 
            }); 
            button.classList.add('border-primary', 'text-primary'); 
            button.classList.remove('border-transparent', 'text-slate-500'); 
            dom.tableContainer.classList.toggle('hidden', view !== 'table'); 
            dom.analyseContainer.classList.toggle('hidden', view !== 'analyse'); 
            dom.templatesContainer.classList.toggle('hidden', view !== 'templates'); 
            dom.expiryContainer.classList.toggle('hidden', view !== 'expiry'); 
            if(view === 'templates') renderTemplateTable(); 
            if(view === 'expiry') renderExpiryTable(); 
            if(view === 'analyse') renderAnalysis(); 
        });

        if (!canManageTemplates) {
            const templatesTab = document.querySelector('button[data-view="templates"]');
            if (templatesTab) templatesTab.style.display = 'none';
        }
        if (!canViewAnalysis) {
            const analyseTab = document.querySelector('button[data-view="analyse"]');
            if (analyseTab) analyseTab.style.display = 'none';
        }

        dom.kanbanContainerParent.querySelectorAll('.kanban-card-container').forEach(container => {
            container.addEventListener('scroll', () => updateScrollFadeEffect(container));
        });
        window.addEventListener('resize', () => {
            dom.kanbanContainerParent.querySelectorAll('.kanban-card-container').forEach(container => {
                updateScrollFadeEffect(container);
            });
        });

        dom.tableBody.addEventListener('click', e => { const toggleBtn = e.target.closest('.toggle-details-btn'); const openModalBtn = e.target.closest('.open-modal-btn'); if (toggleBtn) { e.preventDefault(); const orderId = toggleBtn.dataset.orderId; const detailRow = dom.tableBody.querySelector(`tr[data-detail-for-row="${orderId}"]`); const isHidden = detailRow.classList.contains('hidden'); dom.tableBody.querySelectorAll('.detail-row:not(.hidden)').forEach(row => { if(row !== detailRow) { row.classList.add('hidden'); const prevBtn = dom.tableBody.querySelector(`.toggle-details-btn[data-order-id="${row.dataset.detailForRow}"]`); if(prevBtn) prevBtn.textContent = 'Details'; } }); detailRow.classList.toggle('hidden'); toggleBtn.textContent = isHidden ? 'Details' : 'Schließen'; } else if (openModalBtn) { e.preventDefault(); const orderId = parseInt(openModalBtn.dataset.orderId); const order = orders.find(o => o.id === orderId); if (order) openOrderModal(order); } });
        dom.expiryTableBody.addEventListener('click', e => { const openModalBtn = e.target.closest('.open-modal-btn'); if(openModalBtn) { const orderId = parseInt(openModalBtn.dataset.orderId); const order = orders.find(o => o.id === orderId); if (order) openOrderModal(order); } });
        
        // Listener für Formulare
dom.newOrderForm.addEventListener('submit', async e => { 
            e.preventDefault(); 
            const newOrder = { 
                id: Date.now(), 
                name: document.getElementById('itemName').value, 
                quantity: document.getElementById('quantity').value, 
                requester: document.getElementById('requester').value, 
                status: 'requested', 
                createdAt: new Date(), // Wird beim Senden zu String, Server sollte es handhaben
                notes: document.getElementById('notes').value, 
                isCritical: document.getElementById('isCritical').checked, 
                stdOrderNr: document.getElementById('stdOrderNr').value, 
                supplier: document.getElementById('supplier').value, 
                link: document.getElementById('link').value, 
                storageLocation: document.getElementById('storageLocation').value 
            }; 
            
            // --- NEU: Dedizierte Route nutzen ---
            try {
                const response = await fetch('/api/order_system/add_request', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify(newOrder)
                });
                
                if (response.ok) {
                    // Lokal hinzufügen für sofortiges Feedback
                    orders.push(newOrder); 
                    renderAll(); 
                    closeModal('newOrderModal'); 
                    e.target.reset();
                } else {
                    alert("Fehler beim Senden der Bestellung.");
                }
            } catch(e) { console.error(e); }
        });
        
        dom.createTemplateForm = document.getElementById('createTemplateForm');
        dom.createTemplateForm.addEventListener('submit', async e => {
            e.preventDefault();
            const data = {
                name: document.getElementById('createTemplateItemName').value,
                quantity: document.getElementById('createTemplateQuantity').value,
                packageSize: document.getElementById('createTemplatePackageSize').value,
                notes: document.getElementById('createTemplateNotes').value,
                isCritical: document.getElementById('createTemplateIsCritical').checked,
                stdOrderNr: document.getElementById('createTemplateStdOrderNr').value,
                supplier: document.getElementById('createTemplateSupplier').value,
                link: document.getElementById('createTemplateLink').value,
                storageLocation: document.getElementById('createTemplateStorageLocation').value
            };
            saveTemplateFromData(data);
            closeModal('createTemplateModal');
        });

        dom.editOrderForm.addEventListener('submit', async e => { e.preventDefault(); const orderId = parseInt(document.getElementById('editOrderId').value); const order = orders.find(o => o.id === orderId); if (order) { order.name = document.getElementById('editItemName').value; order.quantity = document.getElementById('editQuantity').value; order.packageSize = document.getElementById('editPackageSize').value; order.notes = document.getElementById('editNotes').value; order.isCritical = document.getElementById('editIsCritical').checked; order.stdOrderNr = document.getElementById('editStdOrderNr').value; order.supplier = document.getElementById('editSupplier').value; order.link = document.getElementById('editLink').value; order.storageLocation = document.getElementById('editStorageLocation').value; if (order.status === 'inprogress' || order.status === 'completed') { order.orderedAt = document.getElementById('editOrderedAt').value; order.eta = document.getElementById('editEta').value; order.banfNr = document.getElementById('editBanfNr').value; } if (order.status === 'completed') { order.deliveredAt = document.getElementById('editDeliveredAt').value; order.batchNumber = document.getElementById('editBatchNumber').value; order.expiryDate = document.getElementById('editExpiryDate').value; order.documentationChecked = document.getElementById('editDocumentationChecked').checked; const editFileInput = document.getElementById('editDocumentationFile'); if (editFileInput.files.length > 0) { order.documentationFile = editFileInput.files[0].name; } } } await saveDataToServer(); renderAll(); closeModal('editOrderModal'); });
        dom.editModal.deleteBtn.addEventListener('click', async () => { const orderId = parseInt(document.getElementById('editOrderId').value); if (confirm('Möchten Sie diese Anforderung wirklich löschen?')) { orders = orders.filter(o => o.id !== orderId); await saveDataToServer(); renderAll(); closeModal('editOrderModal'); } });
        document.getElementById('inProgressForm').addEventListener('submit', async (e) => { e.preventDefault(); if (!pendingStatusChange) return; const order = pendingStatusChange.order; order.status = 'inprogress'; order.orderedAt = document.getElementById('orderedAt').value; order.eta = document.getElementById('eta').value; order.banfNr = document.getElementById('banfNr').value; order.orderedBy = document.getElementById('currentUser').dataset.name; await saveDataToServer(); closeModal('inProgressModal'); pendingStatusChange = null; renderAll(); });
        document.getElementById('completedForm').addEventListener('submit', async (e) => { e.preventDefault(); if (!pendingStatusChange) return; const order = pendingStatusChange.order; order.status = 'completed'; order.deliveredAt = document.getElementById('deliveredAt').value; order.batchNumber = document.getElementById('batchNumber').value; order.expiryDate = document.getElementById('expiryDate').value; order.documentationChecked = document.getElementById('documentationChecked').checked; const fileInput = document.getElementById('documentationFile'); if(fileInput.files.length > 0) { order.documentationFile = fileInput.files[0].name; } else { order.documentationFile = order.documentationFile || ''; } await saveDataToServer(); closeModal('completedModal'); pendingStatusChange = null; renderAll(); });
        dom.tableBody.addEventListener('change', e => { if (e.target.classList.contains('status-select')) { const orderId = parseInt(e.target.dataset.orderId); const newStatus = e.target.value; handleStatusChange(orderId, newStatus); } });

        let draggedCard = null; 
        function addDragAndDropEvents(card) { 
            if (canManageOrders) {
                card.addEventListener('dragstart', (e) => { draggedCard = e.target; setTimeout(() => { e.target.classList.add('opacity-50'); }, 0); }); 
                card.addEventListener('dragend', (e) => { e.target.classList.remove('opacity-50'); draggedCard = null; }); 
            } else {
                card.draggable = false;
                card.style.cursor = 'pointer';
            }
        } 
        Object.values(dom.kanbanContainerParent.querySelectorAll('.kanban-card-container')).forEach(columnContainer => { 
            columnContainer.addEventListener('dragover', e => { e.preventDefault(); }); 
            columnContainer.addEventListener('drop', e => { e.preventDefault(); if (!draggedCard || !canManageOrders) return; const newStatus = columnContainer.closest('.kanban-column').dataset.status; const orderId = parseInt(draggedCard.dataset.orderId); handleStatusChange(orderId, newStatus); }); 
        });

        // Save Helpers
        async function saveTemplateFromData(data) { const { name, quantity, packageSize, notes, isCritical, stdOrderNr, supplier, link, storageLocation } = data; if (!name || !quantity) { alert("Artikel und Menge sind erforderlich."); return; } const alreadyExists = templates.some(t => t.name.toLowerCase() === name.toLowerCase() && t.quantity === quantity); if (alreadyExists) { if (!confirm('Eine sehr ähnliche Vorlage existiert bereits. Trotzdem speichern?')) return; } templates.push({ name, quantity, packageSize, notes, isCritical, stdOrderNr, supplier, link, storageLocation }); await saveDataToServer(); alert(`Vorlage "${name}" wurde gespeichert.`); if (document.body.classList.contains('modal-active') && document.querySelector('#templatesContainer:not(.hidden)')) { renderTemplateTable(); } }
        dom.saveEditAsTemplateBtn.addEventListener('click', () => { saveTemplateFromData({ name: document.getElementById('editItemName').value, quantity: document.getElementById('editQuantity').value, packageSize: document.getElementById('editPackageSize').value, notes: document.getElementById('editNotes').value, isCritical: document.getElementById('editIsCritical').checked, stdOrderNr: document.getElementById('editStdOrderNr').value, supplier: document.getElementById('editSupplier').value, link: document.getElementById('editLink').value, storageLocation: document.getElementById('editStorageLocation').value }); });
        
        function openEditTemplateModal(index) { const template = templates[index]; if (!template) return; document.getElementById('editTemplateIndex').value = index; document.getElementById('editTemplateItemName').value = template.name; document.getElementById('editTemplateQuantity').value = template.quantity; document.getElementById('editTemplatePackageSize').value = template.packageSize || ''; document.getElementById('editTemplateNotes').value = template.notes || ''; document.getElementById('editTemplateIsCritical').checked = template.isCritical; document.getElementById('editTemplateStdOrderNr').value = template.stdOrderNr || ''; document.getElementById('editTemplateSupplier').value = template.supplier || ''; document.getElementById('editTemplateLink').value = template.link || ''; document.getElementById('editTemplateStorageLocation').value = template.storageLocation || ''; openModal('editTemplateModal'); }
        async function deleteTemplate(index) { if (confirm(`Möchten Sie die Vorlage "${templates[index].name}" wirklich löschen?`)) { templates.splice(index, 1); await saveDataToServer(); renderTemplateTable(); } }
        dom.templateTableBody.addEventListener('click', e => { 
            const editBtn = e.target.closest('.edit-template-btn'); 
            const deleteBtn = e.target.closest('.delete-template-btn'); 
            if (editBtn) window.openEditTemplateModal(parseInt(editBtn.dataset.index));
            if (deleteBtn) window.deleteTemplate(parseInt(deleteBtn.dataset.index)); 
        });        
        dom.editTemplateForm.addEventListener('submit', async e => { e.preventDefault(); const index = parseInt(document.getElementById('editTemplateIndex').value); const template = templates[index]; if (template) { template.name = document.getElementById('editTemplateItemName').value; template.quantity = document.getElementById('editTemplateQuantity').value; template.packageSize = document.getElementById('editTemplatePackageSize').value; template.notes = document.getElementById('editTemplateNotes').value; template.isCritical = document.getElementById('editTemplateIsCritical').checked; template.stdOrderNr = document.getElementById('editTemplateStdOrderNr').value; template.supplier = document.getElementById('editTemplateSupplier').value; template.link = document.getElementById('editTemplateLink').value; template.storageLocation = document.getElementById('editTemplateStorageLocation').value; } await saveDataToServer(); closeModal('editTemplateModal'); renderTemplateTable(); });

        window.switchToExpiryView = function() { const expiryTabButton = document.querySelector('button[data-view="expiry"]'); if (expiryTabButton) expiryTabButton.click(); };
        window.cancelStatusChange = function(modalType) { pendingStatusChange = null; closeModal(`${modalType}Modal`); renderAll(); }
        function handleStatusChange(orderId, newStatus) { const order = orders.find(o => o.id === orderId); if (!order || order.status === newStatus) return; pendingStatusChange = { order, oldStatus: order.status, newStatus }; if (newStatus === 'inprogress') { document.getElementById('inProgressItemName').textContent = order.name; document.getElementById('inProgressOrderId').value = order.id; document.getElementById('inProgressForm').reset(); openModal('inProgressModal'); } else if (newStatus === 'completed') { document.getElementById('completedItemName').textContent = order.name; document.getElementById('completedOrderId').value = order.id; document.getElementById('completedForm').reset(); openModal('completedModal'); } else { order.status = newStatus; pendingStatusChange = null; saveDataToServer(); renderAll(); } }
        window.openOrderModal = function(order) { 
            dom.editModal.viewItemName.textContent = order.name; 
            dom.editModal.viewQuantity.textContent = order.quantity;
            dom.editModal.viewPackageSize.textContent = order.packageSize || '';
            dom.editModal.viewStorageLocationContainer.classList.toggle('hidden', !order.storageLocation); 
            dom.editModal.viewStorageLocation.textContent = order.storageLocation || ''; 
            dom.editModal.viewStdOrderNrContainer.classList.toggle('hidden', !order.stdOrderNr); 
            dom.editModal.viewStdOrderNr.textContent = order.stdOrderNr || ''; 
            dom.editModal.viewSupplierContainer.classList.toggle('hidden', !order.supplier); 
            dom.editModal.viewSupplier.textContent = order.supplier || ''; 
            dom.editModal.viewLinkContainer.classList.toggle('hidden', !order.link); 
            if(order.link) { dom.editModal.viewLink.href = order.link; dom.editModal.viewLink.textContent = order.link; } 
            dom.editModal.viewNotesContainer.classList.toggle('hidden', !order.notes); 
            dom.editModal.viewNotes.textContent = order.notes || ''; 
            dom.editModal.viewOrderId.textContent = order.id; 
            dom.editModal.viewRequester.textContent = order.requester; 
            dom.editModal.viewCreatedAt.textContent = new Date(order.createdAt).toLocaleString('de-DE', { day: '2-digit', month: 'long', year: 'numeric', hour: '2-digit', minute:'2-digit' }) + ' Uhr'; 
            dom.editModal.viewCriticalStatus.classList.toggle('hidden', !order.isCritical); 
            dom.editModal.viewInProgressDetails.classList.toggle('hidden', order.status !== 'inprogress' && order.status !== 'completed'); 
            if(order.status === 'inprogress' || order.status === 'completed') { dom.editModal.viewOrderedAt.textContent = new Date(order.orderedAt).toLocaleDateString('de-DE'); dom.editModal.viewEta.textContent = new Date(order.eta).toLocaleDateString('de-DE'); dom.editModal.viewOrderedBy.textContent = order.orderedBy; dom.editModal.viewBanfNrContainer.classList.toggle('hidden', !order.banfNr); dom.editModal.viewBanfNr.textContent = order.banfNr || ''; } 
            dom.editModal.viewCompletedDetails.classList.toggle('hidden', order.status !== 'completed'); 
            dom.editModal.viewDocumentationDetails.classList.toggle('hidden', order.status !== 'completed'); 
            if(order.status === 'completed') { dom.editModal.viewDeliveredAt.textContent = new Date(order.deliveredAt).toLocaleDateString('de-DE'); dom.editModal.viewBatchContainer.style.display = order.batchNumber ? 'block' : 'none'; dom.editModal.viewBatchNumber.textContent = order.batchNumber; dom.editModal.viewExpiryContainer.style.display = order.expiryDate ? 'block' : 'none'; dom.editModal.viewExpiryDate.textContent = order.expiryDate ? new Date(order.expiryDate).toLocaleDateString('de-DE') : ''; if (order.documentationChecked) { dom.editModal.viewDocStatus.innerHTML = '<span class="material-icons-outlined text-green-600 text-base">check_circle</span> Geprüft'; } else { dom.editModal.viewDocStatus.innerHTML = '<span class="material-icons-outlined text-slate-500 text-base">cancel</span> Nicht geprüft'; } dom.editModal.viewDocFileContainer.classList.toggle('hidden', !order.documentationFile); dom.editModal.viewDocFile.textContent = order.documentationFile; } 
            
            const currentUser = document.getElementById('currentUser').dataset.name;
            const canEditThisOrder = canManageOrders || (order.status === 'requested' && order.requester === currentUser);
            
            dom.editModal.switchToEditBtn.style.display = canEditThisOrder ? 'block' : 'none';
            dom.editModal.deleteBtn.style.display = canEditThisOrder ? 'block' : 'none';
            
            document.getElementById('editOrderId').value = order.id; 
            document.getElementById('editItemName').value = order.name; 
            document.getElementById('editQuantity').value = order.quantity;
            document.getElementById('editPackageSize').value = order.packageSize || '';
            document.getElementById('editStdOrderNr').value = order.stdOrderNr || ''; 
            document.getElementById('editSupplier').value = order.supplier || ''; 
            document.getElementById('editLink').value = order.link || ''; 
            document.getElementById('editStorageLocation').value = order.storageLocation || ''; 
            document.getElementById('editRequester').value = order.requester; 
            document.getElementById('editNotes').value = order.notes || ''; 
            document.getElementById('editIsCritical').checked = order.isCritical; 
            window.switchToViewMode(false); 
            openModal('editOrderModal'); 
        };
        window.switchToViewMode = function(resetForm = true) { if (resetForm) { const orderId = parseInt(document.getElementById('editOrderId').value); const order = orders.find(o => o.id === orderId); if (order) { document.getElementById('editItemName').value = order.name; document.getElementById('editQuantity').value = order.quantity; document.getElementById('editPackageSize').value = order.packageSize || ''; document.getElementById('editStdOrderNr').value = order.stdOrderNr || ''; document.getElementById('editSupplier').value = order.supplier || ''; document.getElementById('editLink').value = order.link || ''; document.getElementById('editStorageLocation').value = order.storageLocation || ''; document.getElementById('editNotes').value = order.notes || ''; document.getElementById('editIsCritical').checked = order.isCritical; } } dom.editModal.title.textContent = "Bestelldetails"; dom.editModal.viewContent.classList.remove('hidden'); dom.editModal.editForm.classList.add('hidden'); dom.editModal.switchToEditBtn.classList.remove('hidden'); dom.editModal.deleteBtn.classList.add('hidden'); dom.editModal.editButtons.classList.add('hidden'); dom.editModal.viewButtons.classList.remove('hidden'); };
        window.switchToEditMode = function() { dom.editModal.title.textContent = "Anforderung bearbeiten"; dom.editModal.viewContent.classList.add('hidden'); dom.editModal.editForm.classList.remove('hidden'); dom.editModal.switchToEditBtn.classList.add('hidden'); dom.editModal.deleteBtn.classList.remove('hidden'); dom.editModal.editButtons.classList.remove('hidden'); dom.editModal.viewButtons.classList.add('hidden'); const orderId = parseInt(document.getElementById('editOrderId').value); const order = orders.find(o => o.id === orderId); if (!order) return; dom.editModal.editDocumentationFields.classList.toggle('hidden', order.status !== 'completed'); const editInProgressFields = document.getElementById('editInProgressFields'); const editCompletedFields = document.getElementById('editCompletedFields'); editInProgressFields.classList.add('hidden'); editCompletedFields.classList.add('hidden'); if (order.status === 'inprogress' || order.status === 'completed') { editInProgressFields.classList.remove('hidden'); document.getElementById('editOrderedAt').value = order.orderedAt || ''; document.getElementById('editEta').value = order.eta || ''; document.getElementById('editBanfNr').value = order.banfNr || ''; document.getElementById('editOrderedBy').value = order.orderedBy || ''; } if (order.status === 'completed') { editCompletedFields.classList.remove('hidden'); document.getElementById('editDeliveredAt').value = order.deliveredAt || ''; document.getElementById('editBatchNumber').value = order.batchNumber || ''; document.getElementById('editExpiryDate').value = order.expiryDate || ''; document.getElementById('editDocumentationChecked').checked = order.documentationChecked || false; document.getElementById('editDocumentationFile').value = ''; const currentFileSpan = document.getElementById('currentDocFile'); if (order.documentationFile) { currentFileSpan.textContent = order.documentationFile; currentFileSpan.parentElement.classList.remove('hidden'); } else { currentFileSpan.parentElement.classList.add('hidden'); } } };

        renderAll();
    });
</script>
</body>
</html>
"""

PROJECT_MONITOR_HTML = """
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Testcell Monitor</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>

<script>
    tailwind.config = {
      theme: {
        extend: {
          colors: { "primary": "#0055A4", "primary-active": "#1D88E5", "danger": "#DC2626", "background-light": "#F5F5F5", "text-light": "#333333" },
          fontFamily: { "display": ["Inter", "sans-serif"] },
          borderRadius: { "DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem" },
          keyframes: {
            breathingBlue: { '0%, 100%': { transform: 'scale(1)', boxShadow: '0 0 0 0 rgba(29, 136, 229, 0.5)' }, '50%': { transform: 'scale(1.1)', boxShadow: '0 0 10px 5px rgba(29, 136, 229, 0)' } },
            blinkCaret: { 'from, to': { borderColor: 'transparent' }, '50%': { borderColor: '#0055A4' } }
          },
          animation: { 'breathing-blue': 'breathingBlue 2s ease-in-out infinite', 'blink-caret': 'blinkCaret .75s step-end infinite' }
        },
      },
    }
</script>
<style>
    .material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24 }
    .group[data-expanded="true"] .collapsed-view-dots { display: none; }
    .group[data-expanded="false"] .collapsed-view-dots { display: flex; }
    .group[data-expanded="true"] .expand-icon { transform: rotate(180deg); }
    #loading-text .cursor { display: inline-block; border-right: 3px solid #0055A4; margin-left: 2px; animation: blink-caret .75s step-end infinite; vertical-align: bottom; height: 1.2em; }

    /* Zurück-Button unten rechts */
    .back-home-btn { position: fixed; bottom: 20px; right: 20px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 50%; box-shadow: 0 2px 10px rgba(0,0,0,0.1); cursor: pointer; transition: all 0.3s; z-index: 60; }
    .back-home-btn:hover { background: #fff; transform: scale(1.1); }

    /* Engine Type Filter Buttons */
    .engine-filter-btn { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.875rem; border-radius: 9999px; border: 1px solid #e2e8f0; background: #fff; color: #475569; font-size: 0.8125rem; font-weight: 500; cursor: pointer; transition: all 0.15s; white-space: nowrap; }
    .engine-filter-btn:hover { background: #f1f5f9; border-color: #cbd5e1; }
    .engine-filter-btn.active { background: #0055A4; border-color: #0055A4; color: #fff; }
    .engine-filter-btn .badge { display: inline-flex; align-items: center; justify-content: center; min-width: 1.25rem; height: 1.25rem; padding: 0 0.25rem; border-radius: 9999px; font-size: 0.6875rem; font-weight: 700; background: rgba(0,0,0,0.12); }
    .engine-filter-btn.active .badge { background: rgba(255,255,255,0.3); }
</style>
</head>
<body class="bg-background-light font-display text-text-light">

<!-- Kleiner Zurück-Button -->
<a href="{{ url_for('dashboard_page') }}" class="back-home-btn" title="Zurück zum Haupt-Dashboard">
    <span class="material-symbols-outlined text-primary">home</span>
</a>

<!-- Ladebildschirm -->
<div id="loader" class="fixed inset-0 z-50 flex flex-col items-center justify-center bg-background-light transition-opacity duration-500 ease-in-out">
    <div id="lottie-container" style="width: 200px; height: 200px;"></div>
    <p id="loading-text" class="mt-4 text-lg text-primary font-semibold"></p>
</div>

<!-- Hauptinhalt -->
<div id="dashboard-content" class="opacity-0 invisible transition-opacity duration-500 ease-in-out">
    <div class="relative flex h-auto min-h-screen w-full flex-col">
    <div class="layout-container flex h-full grow flex-col">
    <div class="flex flex-1 justify-center py-5">
    <div class="layout-content-container flex flex-col w-full max-w-7xl flex-1 px-4 sm:px-6 lg:px-8">
    <header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-gray-200 py-4">
        <div class="flex items-center gap-4">
            <!-- HIER WURDE DAS LOGO EINGEFÜGT -->
            <img src="{{ url_for('static', filename='mtu-logo.png') }}" alt="MTU Logo" class="h-10 mix-blend-multiply">
            <h1 class="text-2xl font-bold leading-tight tracking-tight text-primary">Testcell Live Status</h1>
        </div>
        <div class="flex flex-1 justify-end gap-4">
            <label class="relative flex min-w-40 max-w-xs items-center">
                <div class="absolute left-3 text-gray-400"><span class="material-symbols-outlined">search</span></div>
                <input id="searchInput" class="form-input w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg border border-gray-300 bg-background-light py-2 pl-10 pr-4 text-base font-normal leading-normal placeholder-gray-400 focus:border-primary focus:ring-primary" placeholder="Search Projects" value=""/>
            </label>
        </div>
    </header>
    <div class="flex items-center justify-between mt-6 mb-4">
        <div id="engine-filter-bar" class="flex gap-2 overflow-x-auto pb-1">
        </div>
    </div>

    <!-- Hauptcontainer für alle Projekte -->
    <div id="projects-container" class="space-y-4">
        <div id="active-projects" class="space-y-4"></div>
        <div id="completed-separator" class="hidden">
            <button onclick="toggleCompletedProjects()" class="flex items-center gap-2 pt-8 pb-4 group">
                <span class="material-symbols-outlined text-gray-400 transition-transform duration-200" id="completed-toggle-icon">chevron_right</span>
                <h3 class="text-lg font-semibold text-gray-500 group-hover:text-gray-700">Completed Projects <span id="completed-count-badge" class="text-sm font-normal"></span></h3>
            </button>
            <hr class="border-gray-300 mb-4">
        </div>
        <div id="completed-projects" class="space-y-4 hidden"></div>

    <!-- Jinja2 Makro (Korrigiert) -->
        {% macro render_step_recursive(step) %}
            <div class="mt-2 pl-1"> <!-- Kleines Padding links für Einrückung -->
                <div class="flex items-start">
                    {% if step.type == 'deviation' %}
                        <!-- Icon -->
                        <span class="material-symbols-outlined {{ 'text-danger' if step.status != 'Completed' else 'text-primary' }} text-lg -mt-0.5 mr-2 flex-shrink-0">subdirectory_arrow_right</span>
                        
                        <div class="flex-grow min-w-0">
                            <!-- Zeile 1: Titel -->
                            <p class="text-xs mb-0.5 {{ 'text-danger font-semibold' if step.status != 'Completed' else 'text-primary font-semibold' }}">
                                Deviation
                            </p>
                            
                            <!-- Zeile 2: Ticket Nummer (NEU: Eigene Zeile) -->
                                {% if step.deviation_details.hub_incident_number %}
                                    <button onclick="event.stopPropagation(); copyToClipboard('{{ step.deviation_details.hub_incident_number }}', this)" 
                                            class="ml-1 bg-slate-100 text-slate-600 px-1.5 py-0.5 rounded text-[10px] border border-slate-200 font-mono hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200 transition-colors cursor-pointer inline-flex items-center gap-1 group" 
                                            title="Klicken zum Kopieren">
                                        <span class="material-symbols-outlined text-[10px] text-slate-400 group-hover:text-blue-500">content_copy</span>
                                        <span>{{ step.deviation_details.hub_incident_number }}</span>
                                    </button>
                                {% endif %}

                            <!-- Zeile 3: Grund / Beschreibung -->
                            <p class="text-xs text-gray-600 break-words leading-snug">{{ step.deviation_details.reason }}</p>
                            
                            <!-- Zeile 4: Status Text -->
                            <p class="text-[10px] italic {{ 'text-danger' if step.status != 'Completed' else 'text-primary' }} mt-0.5">
                                {{ 'Ongoing' if step.status != 'Completed' else 'Resolved' }}
                            </p>
                        </div>

                    {% else %}
                        <!-- Standard Step (Punkt) -->
                        <div class="w-2 h-2 rounded-full mr-2 mt-1.5 flex-shrink-0
                            {% if step.status == 'In Progress' %} border-2 border-primary-active bg-white animate-breathing-blue
                            {% elif step.status == 'Completed' %} bg-primary
                            {% elif step.status == 'On Hold' %} bg-yellow-400
                            {% else %} bg-gray-300 {% endif %}"></div>
                        
                        <div>
                            <p class="text-xs
                                {% if step.status == 'In Progress' %} text-primary font-semibold
                                {% elif step.status == 'Completed' %} text-gray-600
                                {% elif step.status == 'On Hold' %} text-yellow-600 font-semibold
                                {% elif step.status == 'N/R' %} text-gray-400 line-through
                                {% else %} text-gray-400 {% endif %}">
                                {{ step.name.split('(')[0] }}
                            </p>
                            <!-- Timestamp -->
                            {% if step.last_updated_at %}
                                <div class="text-gray-400 text-[10px] mt-0.5 whitespace-nowrap">
                                    {{ step.last_updated_at }}
                                </div>
                            {% endif %}
                        </div>
                    {% endif %}
                </div>

                <!-- Rekursion für Kinder -->
                {% if step.display_children %}
                    <div class="pl-4 mt-1 border-l-2 {{ 'border-red-100' if step.type == 'deviation' else 'border-gray-100' }} ml-1.5">
                        {% for child in step.display_children %}
                            {{ render_step_recursive(child) }}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>
        {% endmacro %}


        <div id="initial-load-projects" class="hidden">
            {% for project in projects %}
            <div class="project-card group rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-all hover:shadow-md hover:border-primary/50" data-expanded="false" data-status="{{ project.status }}" data-engine-type="{{ project.engine_type }}" onclick="this.setAttribute('data-expanded', this.getAttribute('data-expanded') === 'false' ? 'true' : 'false')">
                <div class="grid grid-cols-12 gap-6 items-center">
                    <div class="col-span-12 md:col-span-2">
                        <div class="flex flex-col">
                            <h2 class="project-code text-xl font-bold text-primary">{{ project.project_code }}</h2>
                            <p class="engine-type text-base font-semibold text-slate-700">{{ project.engine_type }}</p>
                            <div class="mt-2 text-sm text-slate-500 space-y-1">
                                <p><strong class="font-medium">S/N:</strong> <span class="project-serial font-mono">{{ project.engine_serial }}</span></p>
                                <p><strong class="font-medium">Customer:</strong> <span class="project-customer">{{ project.customer }}</span></p>
                            </div>
                        </div>
                    </div>

                    <div class="col-span-12 md:col-span-10">
                        <div class="flex items-center w-full gap-6">
                            <div class="flex-1 flex flex-col">
                                <div class="flex items-start w-full">
                                    {% for step in project.main_steps %}
                                        <div class="flex justify-center transition-all duration-500 
                                            group-data-[expanded=true]:flex-[{{ step.flex_weight }}_1_0%] 
                                            flex-[1_1_0%]"
                                        >
                                            <div class="flex flex-col items-center">
                                                {% if step.status == 'Completed' %}
                                                    <div class="w-5 h-5 rounded-full bg-primary z-10"></div>
                                                {% elif step.is_in_progress %}
                                                    <div class="w-5 h-5 rounded-full border-2 border-primary-active bg-white animate-breathing-blue z-10"></div>
                                                {% elif step.is_on_hold %}
                                                    <div class="w-5 h-5 rounded-full bg-yellow-400 z-10 flex items-center justify-center">
                                                        <span class="material-symbols-outlined text-white leading-none" style="font-size:10px;">pause</span>
                                                    </div>
                                                {% else %}
                                                    <div class="w-5 h-5 rounded-full bg-gray-200 z-10"></div>
                                                {% endif %}

                                                <div class="text-center">
                                                    <p class="text-xs mt-2
                                                        {% if step.is_in_progress %} text-primary-active font-semibold
                                                        {% elif step.status == 'Completed' %} text-gray-800
                                                        {% elif step.is_on_hold %} text-yellow-600 font-semibold
                                                        {% elif step.status == 'N/R' %} text-gray-400 line-through
                                                        {% else %} text-gray-400 {% endif %}">
                                                        {{ step.name.split('(')[0] }}
                                                    </p>
                                                    {% if step.last_updated_at %}
                                                        <div class="text-[10px] text-gray-400 mt-0.5 whitespace-nowrap">
                                                            <p>{{ step.last_updated_at.split(',')[0] }}</p>
                                                            {% if ',' in step.last_updated_at %}
                                                                <p>{{ step.last_updated_at.split(',')[1] }}</p>
                                                            {% endif %}
                                                        </div>
                                                    {% endif %}
                                                </div>

                                                <div class="collapsed-view-dots justify-center mt-2 space-x-1.5">
                                                    {% for dot in step.collapsed_dots %}
                                                        <div class="w-1.5 h-1.5 rounded-full {{ dot.class }}"></div>
                                                    {% endfor %}
                                                </div>
                                            </div>
                                        </div>
                                        {% if not loop.last %}
                                            <div class="flex-1 h-0.5 {{ 'bg-primary' if step.status == 'Completed' else 'bg-gray-200' }} relative top-[10px]"></div>
                                        {% endif %}
                                    {% endfor %}
                                </div>

                                <div class="flex w-full min-h-[1px] max-h-0 opacity-0 pointer-events-none transition-all duration-500 ease-in-out group-data-[expanded=true]:max-h-[500px] group-data-[expanded=true]:opacity-100 group-data-[expanded=true]:mt-4 group-data-[expanded=true]:pointer-events-auto">
                                    {% for step in project.main_steps %}
                                        <div class="px-2 transition-all duration-500 
                                            group-data-[expanded=true]:flex-[{{ step.flex_weight }}_1_0%] 
                                            flex-[1_1_0%]"
                                        >
                                            {% for child in step.display_children %}
                                                {{ render_step_recursive(child) }}
                                            {% endfor %}
                                        </div>
                                        {% if not loop.last %}<div class="flex-1"></div>{% endif %}
                                    {% endfor %}
                                </div>
                            </div>

                            <div class="flex-shrink-0 w-24">
                                <div class="flex justify-end items-center">
                                    {% if project.status == 'completed' %}
                                        <p class="text-sm font-medium text-primary font-bold">Completed</p>
                                    {% else %}
                                        <p class="text-sm font-medium whitespace-nowrap">{{ project.progress }}%</p>
                                    {% endif %}
                                    <button class="ml-2"><span class="material-symbols-outlined text-gray-400 expand-icon transition-transform duration-300">expand_more</span></button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            {% endfor %}
        </div>
    </div>
    </div>
    </div>
    </div>
    </div>
</div>

<script>

    lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer: 'svg', loop: true, autoplay: true, path: '{{ url_for('static', filename='airplane logistics.json') }}' });
    const loadingTextElement = document.getElementById('loading-text');
    const textToType = "Collecting engine data ...";
    let typeIndex = 0;
    function typeWriter() { if (typeIndex < textToType.length) { loadingTextElement.innerHTML = textToType.substring(0, typeIndex + 1) + '<span class="cursor"></span>'; typeIndex++; setTimeout(typeWriter, 100); } else { loadingTextElement.textContent = textToType; } }
    typeWriter();

    let activeEngineFilter = 'all';

    function separateProjectsByStatus() {
        const initialContainer = document.getElementById('initial-load-projects');
        const activeContainer = document.getElementById('active-projects');
        const completedContainer = document.getElementById('completed-projects');
        const separator = document.getElementById('completed-separator');

        // Sortierung: 0%-Projekte ans Ende der aktiven Liste, dann engine_type, dann project_code
        const allProjectCards = Array.from(initialContainer.children).sort((a, b) => {
            const getIsZero = (card) => {
                const el = card.querySelector('.whitespace-nowrap');
                return el && el.textContent.trim() === '0%' ? 1 : 0;
            };
            const zeroA = getIsZero(a), zeroB = getIsZero(b);
            if (zeroA !== zeroB) return zeroA - zeroB;
            const typeA = (a.dataset.engineType || '').toLowerCase();
            const typeB = (b.dataset.engineType || '').toLowerCase();
            if (typeA !== typeB) return typeA.localeCompare(typeB);
            const codeA = (a.querySelector('.project-code') || {}).textContent || '';
            const codeB = (b.querySelector('.project-code') || {}).textContent || '';
            return codeA.localeCompare(codeB);
        });

        let completedCount = 0;
        allProjectCards.forEach(card => {
            if (card.dataset.status === 'completed') { completedContainer.appendChild(card); completedCount++; }
            else { activeContainer.appendChild(card); }
        });
        if (completedCount > 0) { separator.classList.remove('hidden'); }
        initialContainer.remove();
    }

    function updateSeparatorVisibility() {
        const separator = document.getElementById('completed-separator');
        const badge = document.getElementById('completed-count-badge');
        const visibleCompleted = Array.from(document.querySelectorAll('.project-card'))
            .filter(c => c.dataset.status === 'completed' && c.style.display !== 'none').length;
        separator.style.display = visibleCompleted > 0 ? 'block' : 'none';
        if (badge) badge.textContent = visibleCompleted > 0 ? `(${visibleCompleted})` : '';
    }

    function toggleCompletedProjects() {
        const list = document.getElementById('completed-projects');
        const icon = document.getElementById('completed-toggle-icon');
        const isHidden = list.classList.toggle('hidden');
        icon.style.transform = isHidden ? '' : 'rotate(90deg)';
    }

    // Longest-prefix-match: findet den spezifischsten Template-Key für einen Engine Type
    function getTemplateKey(engineType, templateKeys) {
        return templateKeys
            .filter(k => engineType.startsWith(k))
            .sort((a, b) => b.length - a.length)[0] || null;
    }

    function filterByEngineType(type, templateKeys) {
        activeEngineFilter = type;
        const keys = templateKeys || window._engineTemplateKeys || [];
        const searchTerm = (document.getElementById('searchInput').value || '').toLowerCase();
        document.querySelectorAll('.project-card').forEach(card => {
            const engineType = card.dataset.engineType || '';
            const typeMatch = type === 'all' || getTemplateKey(engineType, keys) === type;
            const codeText = (card.querySelector('.project-code') || {}).textContent?.toLowerCase() || '';
            const serialText = (card.querySelector('.project-serial') || {}).textContent?.toLowerCase() || '';
            const customerText = (card.querySelector('.project-customer') || {}).textContent?.toLowerCase() || '';
            const engineTypeText = (card.querySelector('.engine-type') || {}).textContent?.toLowerCase() || '';
            const searchMatch = !searchTerm || codeText.includes(searchTerm) || serialText.includes(searchTerm) || customerText.includes(searchTerm) || engineTypeText.includes(searchTerm);
            card.style.display = (typeMatch && searchMatch) ? 'block' : 'none';
        });
        updateSeparatorVisibility();
        document.querySelectorAll('.engine-filter-btn').forEach(btn => {
            btn.classList.toggle('active', btn.dataset.type === type);
        });
    }

    async function buildEngineFilterBar() {
        const bar = document.getElementById('engine-filter-bar');
        if (!bar) return;

        // Engine Types aus process_templates laden
        let engineTypes = [];
        try {
            const res = await fetch("{{ url_for('api_get_process_templates') }}");
            const data = await res.json();
            engineTypes = Object.keys(data).sort();
        } catch (err) {
            // Fallback: direkte Engine Types aus dem DOM
            engineTypes = [...new Set(Array.from(document.querySelectorAll('.project-card'))
                .map(c => c.dataset.engineType || '').filter(Boolean))].sort();
        }
        // Global speichern damit filterByEngineType ohne Übergabe funktioniert
        window._engineTemplateKeys = engineTypes;

        // Aktive Projekte je Template-Key zählen (longest-prefix-match)
        const counts = {};
        let totalActive = 0;
        document.querySelectorAll('.project-card').forEach(card => {
            if (card.dataset.status !== 'completed') {
                totalActive++;
                const key = getTemplateKey(card.dataset.engineType || '', engineTypes);
                if (key) counts[key] = (counts[key] || 0) + 1;
            }
        });

        // "Alle"-Button
        const allBtn = document.createElement('button');
        allBtn.className = 'engine-filter-btn active';
        allBtn.dataset.type = 'all';
        allBtn.innerHTML = `Alle <span class="badge">${totalActive}</span>`;
        allBtn.onclick = () => filterByEngineType('all', engineTypes);
        bar.appendChild(allBtn);

        // Ein Button je Engine Type (nur wenn Projekte vorhanden)
        engineTypes.forEach(type => {
            const count = counts[type] || 0;
            if (count === 0) return;
            const btn = document.createElement('button');
            btn.className = 'engine-filter-btn';
            btn.dataset.type = type;
            btn.innerHTML = `${type} <span class="badge">${count}</span>`;
            btn.onclick = () => filterByEngineType(type, engineTypes);
            bar.appendChild(btn);
        });
    }
    
    function copyToClipboard(text, btnElement) {
        navigator.clipboard.writeText(text).then(() => {
            // Visuelles Feedback
            const originalHtml = btnElement.innerHTML;
            const originalClasses = btnElement.className;
            
            // Success State
            btnElement.classList.remove('bg-slate-100', 'text-slate-600', 'border-slate-200');
            btnElement.classList.add('bg-green-100', 'text-green-700', 'border-green-200');
            btnElement.innerHTML = `<span class="material-symbols-outlined text-[10px]">check</span> Kopiert!`;
            
            // Reset nach 1.5 Sekunden
            setTimeout(() => {
                btnElement.className = originalClasses;
                btnElement.innerHTML = originalHtml;
            }, 1500);
        }).catch(err => {
            console.error('Kopieren fehlgeschlagen:', err);
        });
    }
    function showDashboard() {
        separateProjectsByStatus();
        buildEngineFilterBar();
        const loaderDiv = document.getElementById('loader');
        const dashboard = document.getElementById('dashboard-content');
        loaderDiv.classList.add('opacity-0', 'invisible');
        dashboard.classList.remove('opacity-0', 'invisible');
    }

    setTimeout(showDashboard, 2000);

    document.addEventListener('DOMContentLoaded', function () {
        const searchInput = document.getElementById('searchInput');
        searchInput.addEventListener('keyup', function (event) {
            const searchTerm = event.target.value.toLowerCase();
            const allCards = document.querySelectorAll('.project-card');
            allCards.forEach(function (card) {
                const typeMatch = activeEngineFilter === 'all' || getTemplateKey(card.dataset.engineType || '', window._engineTemplateKeys || []) === activeEngineFilter;
                const codeText = (card.querySelector('.project-code') || {}).textContent?.toLowerCase() || '';
                const serialText = (card.querySelector('.project-serial') || {}).textContent?.toLowerCase() || '';
                const customerText = (card.querySelector('.project-customer') || {}).textContent?.toLowerCase() || '';
                const engineTypeText = (card.querySelector('.engine-type') || {}).textContent?.toLowerCase() || '';
                const searchMatch = !searchTerm || codeText.includes(searchTerm) || serialText.includes(searchTerm) || customerText.includes(searchTerm) || engineTypeText.includes(searchTerm);
                card.style.display = (typeMatch && searchMatch) ? 'block' : 'none';
            });
            updateSeparatorVisibility();
        });
    });
</script>

</body></html>
"""

PROJECT_DETAIL_PAGE_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MTU – Projektdetails</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Mono:wght@400;600&display=swap" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms"></script>
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .font-mono { font-family: 'Noto Sans Mono', monospace; }
        .material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; }

        /* Sidebar */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }

        /* Step Tree */
        .project-step-grid { display: grid; grid-template-columns: auto 1fr; gap: 0 0.75rem; align-items: start; padding: 0.375rem 0.5rem; border-radius: 0.375rem; border: 1px solid transparent; transition: all 0.1s; cursor: pointer; }
        .project-step-grid:hover { background-color: #f8fafc; border-color: #e2e8f0; }
        .project-step-grid.selected-step { background-color: #eff6ff; border-color: #bfdbfe; box-shadow: inset 3px 0 0 #2563eb; }
        .project-step-icon-col { display: flex; flex-direction: column; align-items: center; height: 100%; }
        .project-step-icon-connector { width: 2px; flex-grow: 1; margin: 0.25rem 0; }
        .project-step-icon-circle { display: flex; align-items: center; justify-content: center; width: 2.2rem; height: 1.5rem; border-radius: 9999px; font-size: 0.7rem; font-weight: 700; box-shadow: 0 0 0 4px white; z-index: 2; }
        .expand-toggle-btn { transition: transform 0.2s; }

        /* Cycle groups */
        .cycle-group { border-radius: 0.5rem; margin-bottom: 0.25rem; }
        .cycle-group.cycle-old { opacity: 0.45; background-color: rgba(0,0,0,0.02); padding: 0 6px 4px 6px; }
        .cycle-group.cycle-current { padding: 0 2px; }
        .cycle-header { display: flex; align-items: center; gap: 6px; padding: 6px 0 4px 0; cursor: pointer; user-select: none; }
        .cycle-header:hover .cycle-label { color: #64748b; }
        .cycle-divider-line { flex: 1; height: 1px; background: #e2e8f0; }
        .cycle-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap; }
        .cycle-toggle-icon { font-size: 14px; color: #94a3b8; transition: transform 0.2s; }
        .cycle-group.cycle-collapsed .cycle-toggle-icon { transform: rotate(-90deg); }
        .cycle-group.cycle-collapsed .cycle-body { display: none; }
        .cycle-badge { font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px; white-space: nowrap; }
        .cycle-badge-done { background: #dcfce7; color: #166534; }
        .cycle-badge-active { background: #dbeafe; color: #1d4ed8; }
        .new-cycle-btn { display: flex; align-items: center; gap: 6px; margin-top: 12px; padding: 7px 14px; border-radius: 8px; border: 1.5px dashed #cbd5e1; background: transparent; color: #64748b; font-size: 12px; font-weight: 600; cursor: pointer; width: 100%; justify-content: center; transition: all 0.15s; }
        .new-cycle-btn:hover { border-color: #3b82f6; color: #2563eb; background: #eff6ff; }
        /* New cycle dialog */
        #newCycleDialog { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.35); }
        #newCycleDialog .dlg-box { background: white; border-radius: 12px; padding: 24px; max-width: 420px; width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.2); }
        #newCycleDialog .dlg-warning { background: #fef9c3; border: 1px solid #fde047; border-radius: 8px; padding: 10px 14px; margin-bottom: 16px; font-size: 13px; color: #713f12; }
        #newCycleDialog .dlg-copy-list { max-height: 200px; overflow-y: auto; margin-bottom: 16px; }
        #newCycleDialog label { display: flex; align-items: center; gap: 8px; padding: 6px 4px; font-size: 13px; cursor: pointer; border-radius: 6px; }
        #newCycleDialog label:hover { background: #f8fafc; }
        #newCycleDialog .dlg-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }

        /* Checklist */
        .instruction-row { transition: all 0.3s ease; }
        .instruction-row.is-checked { background-color: #f8fafc; opacity: 0.7; }
        .instruction-text.is-checked { text-decoration: line-through; color: #94a3b8; }
        .instruction-row.is-disabled { opacity: 0.5; pointer-events: none; filter: grayscale(100%); }
        .instruction-row.is-nr { background-color: #f8fafc; opacity: 0.6; }
        .instruction-text.is-nr { text-decoration: line-through; color: #94a3b8; }
        .instruction-nr-btn { opacity: 0; transition: opacity 0.15s; flex-shrink: 0; }
        .instruction-row:hover .instruction-nr-btn { opacity: 1; }
        .instruction-row.is-nr .instruction-nr-btn { opacity: 1; }
        @keyframes shake { 0%,100%{transform:translateX(0)} 20%,60%{transform:translateX(-5px);border-color:#ef4444;background-color:#fef2f2} 40%,80%{transform:translateX(5px);border-color:#ef4444;background-color:#fef2f2} }
        .shake-error { animation: shake 0.5s ease-in-out; border-color: #ef4444 !important; border-left-width: 4px !important; border-left-color: #ef4444 !important; }

        /* Tree column resizable */
        #treeCol { width: var(--tree-width, 460px); min-width: 200px; max-width: 600px; position: relative; flex-shrink: 0; }
        #treeCol.is-resizing { transition: none; }
        .tree-resizer { position: absolute; right: -3px; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 50; display: flex; align-items: center; justify-content: center; }
        .tree-resizer:hover, .tree-resizer.is-resizing { background-color: #3b82f6; }
        .tree-resizer::after { content:""; height:24px; width:2px; background-color:#cbd5e1; border-radius:1px; transition:background-color 0.2s; }
        .tree-resizer:hover::after, .tree-resizer.is-resizing::after { background-color: white; }

        /* Inspector */
        #inspectorCol { width: var(--inspector-width, 420px); min-width: var(--inspector-width, 420px); max-width: 900px; transition: width 0.4s cubic-bezier(0.4,0,0.2,1), min-width 0.4s cubic-bezier(0.4,0,0.2,1); position: relative; }
        #inspectorCol.is-resizing { transition: none; }
        #inspectorCol.collapsed { width: 0 !important; min-width: 0 !important; overflow: hidden; border-left-width: 0; }
        .inspector-resizer { position: absolute; left: -3px; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 50; display: flex; align-items: center; justify-content: center; }
        .inspector-resizer:hover, .inspector-resizer.is-resizing { background-color: #3b82f6; }
        .inspector-resizer::after { content:""; height:24px; width:2px; background-color:#cbd5e1; border-radius:1px; transition:background-color 0.2s; }
        .inspector-resizer:hover::after, .inspector-resizer.is-resizing::after { background-color: white; }
        #inspectorCol .custom-scrollbar::-webkit-scrollbar { width: 8px; }
        #inspectorCol .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
        #inspectorCol .custom-scrollbar::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; border: 2px solid transparent; background-clip: content-box; }

        @keyframes slideUp { from{transform:translateY(100%);opacity:0} to{transform:translateY(0);opacity:1} }
        .animate-slide-up { animation: slideUp 0.3s cubic-bezier(0.16,1,0.3,1) forwards; }
        #commentsPanel { transition: max-height 0.3s ease, opacity 0.3s ease; max-height: 0; overflow: hidden; opacity: 0; }
        #commentsPanel.open { max-height: 400px; opacity: 1; overflow-y: auto; }
        #laufkartenPanel { transition: max-height 0.3s ease, opacity 0.3s ease; max-height: 0; overflow: hidden; opacity: 0; }
        #laufkartenPanel.open { max-height: 500px; opacity: 1; overflow-y: auto; }
        .lk-panel-cycle { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 4px 0; }
        .lk-panel-cycle:first-child { margin-top: 2px; }
        .lk-panel-box { background: white; border: 1px solid #e2e8f0; border-radius: 6px; padding: 6px 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
        .lk-panel-row { display: flex; align-items: baseline; gap: 8px; padding: 2px 0; border-bottom: 1px solid #f8fafc; }
        .lk-panel-row:last-child { border-bottom: none; }
        .lk-panel-label { font-size: 11px; font-weight: 700; color: #334155; flex-shrink: 0; width: 120px; }
        .lk-panel-input { flex: 1; font-size: 10px; border: none; background: transparent; color: #64748b; outline: none; font-family: inherit; padding: 1px 0; min-width: 0; }
        .lk-panel-input::placeholder { color: #cbd5e1; }
        .lk-panel-input:focus { color: #334155; }

        /* Status Dropdown Chip in Tree (M3 Filter Chip with dropdown) */
        .step-status-chip { display: inline-flex; align-items: center; gap: 3px; height: 28px; padding: 0 8px 0 6px; border-radius: 8px; border: 1px solid; cursor: pointer; transition: filter 0.15s; flex-shrink: 0; font-size: 11px; font-weight: 500; white-space: nowrap; }
        .step-status-chip:hover { filter: brightness(0.94); }

        /* Status Dropdown */
        #statusDropdown { position: fixed; z-index: 300; background: white; border: 1px solid #e2e8f0; border-radius: 0.5rem; box-shadow: 0 10px 40px -8px rgba(0,0,0,0.18), 0 2px 8px -2px rgba(0,0,0,0.08); padding: 4px; min-width: 175px; }
        #statusDropdown button { display: flex; align-items: center; gap: 0.5rem; width: 100%; padding: 0.45rem 0.65rem; text-align: left; font-size: 0.8125rem; font-weight: 500; color: #334155; border-radius: 0.375rem; transition: background-color 0.1s; white-space: nowrap; border: none; background: none; cursor: pointer; }
        #statusDropdown button:hover { background-color: #f1f5f9; color: #0f172a; }
        #statusDropdown hr { border: none; border-top: 1px solid #f1f5f9; margin: 3px 0; }

        /* Checklist Action Header */
        #checklistActionHeader { position: sticky; top: 0; z-index: 10; }
    </style>
</head>
<body class="bg-white h-screen flex overflow-hidden">

    {% include 'sidebar.html' %}

    <!-- Haupt-Layout: Tree + Main + Inspector -->
    <div class="flex flex-1 overflow-hidden bg-slate-50">

<!-- SPALTE 1: BAUM -->
<div id="treeCol" class="bg-white border-r border-slate-200 flex flex-col h-full z-10">
    <div id="treeResizer" class="tree-resizer" title="Breite anpassen"></div>
    <!-- Zurück-Link -->
    <div class="px-4 pt-3 pb-0 flex-shrink-0">
        <a href="{{ back_url }}" class="inline-flex items-center gap-1 text-xs text-slate-400 hover:text-blue-600 transition-colors">
            <span class="material-symbols-outlined text-[15px]">arrow_back</span>
            Zurück zur Timeline
        </a>
    </div>
    <div class="px-5 py-5 border-b border-slate-200 bg-white flex-shrink-0">
        <div id="projectHeaderLoading" class="animate-pulse space-y-2">
            <div class="h-5 bg-slate-200 rounded w-3/4"></div>
            <div class="h-3 bg-slate-100 rounded w-1/2"></div>
            <div class="h-2 bg-slate-200 rounded-full mt-4"></div>
        </div>
        <div id="projectHeader" class="hidden">
            <h1 class="text-xl font-bold text-slate-900 tracking-tight leading-snug" id="headerProjectName"></h1>
            <div class="text-[11px] text-slate-400 font-mono mt-1 mb-4" id="headerProjectMeta"></div>
            <div class="flex items-center gap-2.5">
                <span class="text-slate-400 text-[10px] font-semibold uppercase tracking-wide shrink-0">Fortschritt</span>
                <div class="flex-1 rounded-full bg-slate-100 h-1.5 overflow-hidden border border-slate-200">
                    <div class="h-full rounded-full bg-blue-500 transition-all duration-500" id="headerProgressBar" style="width:0%"></div>
                </div>
                <span class="text-blue-600 text-[11px] font-bold shrink-0" id="headerProgressPct">0%</span>
            </div>
            <div class="mt-4 flex items-center gap-3">
                <button onclick="toggleComments()" class="flex items-center gap-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors">
                    <span class="material-symbols-outlined text-[14px]">chat</span>
                    <span id="commentsBtnLabel">Kommentare</span>
                </button>
                <button id="laufkartenBtn" onclick="toggleLaufkartenPanel()" class="hidden flex items-center gap-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors">
                    <span class="material-symbols-outlined text-[14px]">assignment</span>
                    <span id="laufkartenBtnLabel">Laufkarten</span>
                </button>
                <a id="reportPageLink" href="#" class="flex items-center gap-1.5 text-xs text-slate-400 hover:text-blue-600 transition-colors">
                    <span class="material-symbols-outlined text-[14px]">summarize</span>
                    Engineer Report
                </a>
            </div>
        </div>
    </div>

    <!-- Kommentare Panel -->
    <div id="commentsPanel" class="border-b border-slate-200 bg-slate-50 px-4 overflow-y-auto">
        <div id="commentsContent" class="py-3 space-y-2 text-sm text-slate-600"></div>
    </div>

    <!-- Laufkarten Panel -->
    <div id="laufkartenPanel" class="border-b border-slate-200 bg-slate-50 px-4 overflow-y-auto">
        <div id="laufkartenContent" class="py-3"></div>
    </div>

    <div id="stepsScrollCol" class="flex-1 overflow-y-auto px-3 py-5">
        <div class="animate-pulse space-y-2 p-2">
            <div class="h-8 bg-slate-100 rounded"></div>
            <div class="h-8 bg-slate-100 rounded ml-4"></div>
            <div class="h-8 bg-slate-100 rounded ml-4"></div>
        </div>
    </div>
    <div id="treeScrollFade" class="pointer-events-none absolute bottom-0 left-0 right-0 h-10 bg-gradient-to-t from-white to-transparent transition-opacity duration-200"></div>
</div>

<!-- SPALTE 2: MITTLERER BEREICH -->
<div class="flex-1 flex flex-col h-full relative bg-slate-50 overflow-hidden">

    <div id="emptyState" class="absolute inset-0 flex flex-col items-center justify-center z-20 bg-slate-50">
        <div class="w-16 h-16 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center mb-4">
            <span class="material-symbols-outlined" style="font-size:32px;">account_tree</span>
        </div>
        <h2 class="text-lg font-bold text-slate-800">Projektstruktur</h2>
        <p class="text-slate-500 text-sm">Bitte links einen Arbeitsschritt auswählen.</p>
    </div>

    <!-- Inspector toggle (floating) -->
    <button onclick="toggleInspector()" id="toggleInspectorBtn" class="hidden absolute top-4 right-4 z-10 flex items-center gap-1.5 px-2 py-1.5 border border-slate-200 rounded-md bg-white hover:bg-slate-50 text-slate-700 transition-colors shadow-sm">
        <span class="material-symbols-outlined text-[16px]" id="inspectorToggleIcon">right_panel_open</span>
        <span id="inspectorBadge" class="hidden bg-red-100 text-red-700 py-0.5 px-1.5 rounded-full text-[9px] font-bold"></span>
    </button>

    <div class="flex-1 overflow-y-auto pt-16 px-7 pb-4 relative" id="mainScrollArea">
        <div id="chapterState" class="hidden flex flex-col items-center justify-center py-20 text-center">
        </div>

        <div class="max-w-3xl mx-auto" id="taskState">
            <div id="consumablesList" class="hidden bg-white border border-blue-200 rounded-lg shadow-sm mb-5 overflow-hidden">
                <button type="button" id="consumablesToggleBtn" onclick="togglePanelBody('consumablesBody','consumablesChevron')" class="w-full bg-blue-50/50 px-3 py-2.5 flex items-center gap-2 hover:bg-blue-50 transition-colors cursor-pointer">
                    <span class="material-symbols-outlined text-blue-600 text-[17px]">hardware</span>
                    <h3 class="text-xs font-bold text-blue-800 uppercase tracking-wide flex-1 text-left">Hardware & Consumables</h3>
                    <span id="consumablesCountBadge" class="text-[10px] font-mono font-bold text-blue-400 mr-1"></span>
                    <span id="consumablesChevron" class="material-symbols-outlined text-blue-400 text-[18px] transition-transform duration-200">expand_more</span>
                </button>
                <div id="consumablesBody" class="hidden border-t border-blue-100">
                    <ul class="divide-y divide-blue-50 px-3 py-2 text-sm text-slate-700" id="consumablesListItems"></ul>
                </div>
            </div>
            <div id="referencesList" class="hidden bg-white border border-violet-200 rounded-lg shadow-sm mb-5 overflow-hidden">
                <button type="button" onclick="togglePanelBody('referencesBody','referencesChevron')" class="w-full bg-violet-50/60 px-4 py-2.5 flex items-center gap-2 hover:bg-violet-50 transition-colors cursor-pointer">
                    <span class="material-symbols-outlined text-violet-600 text-[17px]">library_books</span>
                    <h3 class="text-xs font-bold text-violet-800 uppercase tracking-widest flex-1 text-left">Applicable Documents</h3>
                    <span id="referencesCountBadge" class="text-[10px] font-mono font-bold text-violet-400 mr-1"></span>
                    <span id="referencesChevron" class="material-symbols-outlined text-violet-400 text-[18px] transition-transform duration-200">expand_more</span>
                </button>
                <div id="referencesBody" class="hidden border-t border-violet-100">
                    <table class="w-full text-xs" id="referencesTable">
                        <thead>
                            <tr class="border-b border-violet-100 bg-violet-50/30">
                                <th class="text-left px-4 py-1.5 font-bold text-violet-700 uppercase tracking-wider w-20">Type</th>
                                <th class="text-left px-4 py-1.5 font-bold text-violet-700 uppercase tracking-wider w-28">Ref. No.</th>
                                <th class="text-left px-4 py-1.5 font-bold text-violet-700 uppercase tracking-wider w-24">Chapter</th>
                                <th class="text-left px-4 py-1.5 font-bold text-violet-700 uppercase tracking-wider">Title / Subject</th>
                            </tr>
                        </thead>
                        <tbody id="referencesTableBody"></tbody>
                    </table>
                </div>
            </div>
            <div id="checklistContainer" class="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden"></div>
        </div>
    </div>


</div>

<!-- SPALTE 3: INSPECTOR -->
<div id="inspectorCol" class="bg-white border-l border-slate-200 flex flex-col h-full flex-shrink-0 z-20 shadow-[-4px_0_15px_rgba(0,0,0,0.03)] collapsed">
    <div id="inspectorResizer" class="inspector-resizer" title="Breite anpassen"></div>

    <div class="px-4 py-3 border-b border-slate-200 bg-slate-50 flex-shrink-0">
        <div class="flex justify-between items-center">
            <h3 id="inspectorMainTitle" class="text-sm font-bold text-slate-800 flex items-center gap-1.5">
                <span class="material-symbols-outlined text-red-500 text-[18px]">warning</span> Deviations & Issues
            </h3>
            <button onclick="toggleInspector()" class="text-slate-400 hover:text-slate-700 p-1">
                <span class="material-symbols-outlined text-[20px]">close</span>
            </button>
        </div>
    </div>

    <div id="inspectorListMode" class="flex-1 overflow-y-auto p-4 bg-slate-50/50 custom-scrollbar">
        <div id="deviationsContainer" class="space-y-4"></div>
    </div>

    <div id="inspectorFormMode" class="flex-1 overflow-y-auto p-5 bg-yellow-50/30 hidden flex flex-col custom-scrollbar">
        <h4 class="font-bold text-yellow-900 mb-4 text-base">Neue Deviation erfassen</h4>
        <div class="space-y-3 flex-1">
            <label class="flex flex-col"><span class="text-xs font-semibold text-slate-600 mb-1">Kurzbeschreibung (Problem)</span>
                <input id="devReasonInput" type="text" class="form-input border-slate-300 rounded-md text-sm shadow-sm" placeholder="Was ist das Problem?">
            </label>
            <label class="flex flex-col"><span class="text-xs font-semibold text-slate-600 mb-1">Detaillierte Beobachtungen</span>
                <textarea id="devDetailsInput" class="form-input min-h-[80px] border-slate-300 rounded-md text-sm shadow-sm" placeholder="Was genau ist passiert?"></textarea>
            </label>
            <div class="flex flex-col gap-1.5">
                <span class="text-xs font-semibold text-slate-600">Optionen</span>
                <label class="flex items-center gap-2 p-2 border border-slate-200 bg-white rounded-md cursor-pointer shadow-sm hover:bg-slate-50">
                    <input id="createHubIncidentCheckbox" type="checkbox" class="text-blue-600 rounded border-slate-300" onchange="document.getElementById('hubIncidentTypeWrapper').classList.toggle('hidden',!this.checked)">
                    <span class="text-sm font-medium text-slate-700">MTU.Hub Incident Ticket anfordern</span>
                </label>
                <div id="hubIncidentTypeWrapper" class="hidden ml-2 p-3 bg-blue-50 border border-blue-200 rounded-md flex flex-col gap-2">
                    <span class="text-[11px] font-semibold text-blue-700 uppercase tracking-wider">Ticket-Art</span>
                    <select id="hubIncidentTypeSelect" class="form-select border-slate-300 rounded-md text-sm shadow-sm">
                        <option value="befund">Befundbeauftragung</option>
                        <option value="schuettware">Schüttware</option>
                        <option value="bauteil">Bauteil</option>
                    </select>
                </div>
                <label class="flex items-center gap-2 p-2 border border-slate-200 bg-white rounded-md cursor-pointer shadow-sm hover:bg-slate-50">
                    <input id="checkPartsCheckbox" type="checkbox" class="text-blue-600 rounded border-slate-300" onchange="toggleDevPpeText(this)">
                    <span class="text-sm font-medium text-slate-700">PPE involvieren &amp; Teilecheck</span>
                </label>
                <label class="flex items-center gap-2 p-2 border border-slate-200 bg-white rounded-md cursor-pointer shadow-sm hover:bg-slate-50">
                    <input id="shopSupportCheckbox" type="checkbox" class="text-blue-600 rounded border-slate-300" onchange="toggleDevShopSupportText(this)">
                    <span class="text-sm font-medium text-slate-700">Shop Support involvieren</span>
                </label>
            </div>
        </div>
        <div class="pt-4 flex justify-end gap-2 border-t border-yellow-200 mt-4">
            <button onclick="showInspectorList()" class="px-3 py-1.5 bg-white border border-slate-300 text-slate-600 rounded text-sm font-medium shadow-sm hover:bg-slate-50">Abbrechen</button>
            <button onclick="saveNewDeviation()" class="px-3 py-1.5 bg-yellow-500 text-yellow-950 rounded text-sm font-bold shadow-sm hover:bg-yellow-400">Speichern</button>
        </div>
    </div>

    <div id="inspectorActionFormMode" class="flex-1 overflow-y-auto p-5 bg-orange-50/30 hidden flex flex-col custom-scrollbar">
        <h4 class="font-bold text-orange-900 mb-4 text-base" id="actionFormTitle"></h4>
        <div class="space-y-3 flex-1">
            <input type="hidden" id="currentDeviationId">
            <div id="actionFormRefWrapper" class="hidden flex-col mb-3">
                <span class="text-xs font-semibold text-slate-600 mb-1">Bezug auf Maßnahme (optional)</span>
                <select id="actionFormRefSelect" class="form-select border-slate-300 rounded-md text-sm shadow-sm"></select>
            </div>
            <label class="flex flex-col"><span class="text-xs font-semibold text-slate-600 mb-1" id="actionFormLabel"></span>
                <textarea class="form-input min-h-[100px] border-slate-300 rounded-md text-sm shadow-sm" id="actionFormTextarea"></textarea>
            </label>
            <div class="mt-2">
                <div class="flex items-center gap-2 mb-2">
                    <span class="text-xs font-semibold text-slate-600">Bilder (optional)</span>
                    <button type="button" onclick="document.getElementById('actionFormFileInput').click()" class="text-xs text-slate-500 hover:text-slate-700 flex items-center gap-1 border border-slate-300 rounded px-2 py-0.5 bg-white hover:bg-slate-50"><span class="material-symbols-outlined text-sm">add_photo_alternate</span> Bild hinzufügen</button>
                    <input type="file" id="actionFormFileInput" accept="image/*" multiple class="hidden" onchange="stageActionFiles(this)">
                </div>
                <div id="actionFormAttachmentPreview" class="flex flex-wrap gap-2"></div>
            </div>
        </div>
        <div class="pt-4 flex justify-end gap-2 border-t border-slate-200 mt-4">
            <button onclick="showInspectorList()" class="px-3 py-1.5 bg-white border border-slate-300 text-slate-600 rounded text-sm font-medium shadow-sm hover:bg-slate-50">Abbrechen</button>
            <button class="px-3 py-1.5 bg-orange-500 text-orange-950 rounded text-sm font-bold shadow-sm hover:bg-orange-400" id="actionFormSaveBtn">Speichern</button>
        </div>
    </div>

    <div id="inspectorImageMode" class="flex-1 overflow-hidden bg-slate-900 hidden flex-col custom-scrollbar">
        <div class="flex-1 p-2 flex flex-col items-center justify-center min-h-0">
            <div id="inspectorImageContent" class="flex items-center justify-center w-full h-full overflow-hidden" style="cursor:default;"></div>
        </div>
        <div class="p-4 border-t border-slate-800 bg-slate-950 flex flex-col gap-2">
            <p class="text-center text-[10px] text-slate-500 select-none">Scrollen = Zoom &nbsp;·&nbsp; Ziehen = Verschieben &nbsp;·&nbsp; Doppelklick = Zurücksetzen</p>
            <button onclick="showInspectorList()" class="w-full py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-lg text-sm font-medium flex items-center justify-center gap-2">
                <span class="material-symbols-outlined text-[18px]">arrow_back</span> Zurück
            </button>
        </div>
    </div>
</div>

</div><!-- end flex row -->

<script>
const PROJECT_UUID = "{{ project_uuid }}";
const SAVE_API_URL = "/api/project_status/";
const LOAD_API_URL = "/api/project_by_uuid/";
const COMMENTS_API_BASE = "/api/comments/";
const LOGGED_IN_USER = "{{ current_user }}";
const IS_ADMIN = {{ 'true' if session.get('is_admin') else 'false' }};

// --- Sidebar collapse ---
(function() {
    const sidebar = document.getElementById('sidebar');
    const btn = document.getElementById('sidebar-toggle');
    if (!sidebar || !btn) return;
    const icon = btn.querySelector('.material-icons-outlined');
    const apply = (collapsed) => {
        sidebar.classList.toggle('is-collapsed', collapsed);
        if (icon) icon.textContent = collapsed ? 'menu' : 'chevron_left';
    };
    btn.addEventListener('click', () => {
        const now = !sidebar.classList.contains('is-collapsed');
        localStorage.setItem('sidebarCollapsed', now);
        apply(now);
    });
    apply(localStorage.getItem('sidebarCollapsed') === 'true');
})();

let projectData = null;
let currentSelectedStepIdStr = null;
let commentsOpen = false;
let actionFormStagedFiles = [];
let cachedComments = [];
let isSaving = false;
let openKebabId = null;
let editingDeviationId = null;
let editingThreadKey = null;
let editHubNrId = null;

document.addEventListener('click', () => {
    if (openKebabId !== null) {
        openKebabId = null;
        const step = findStepById(currentSelectedStepIdStr);
        if (step) renderDeviations(step);
    }
});

// --- Drag-to-Resize Inspector ---
const resizer = document.getElementById('inspectorResizer');
const inspectorCol = document.getElementById('inspectorCol');
let isResizing = false;
if (resizer && inspectorCol) {
    resizer.addEventListener('mousedown', e => { isResizing = true; inspectorCol.classList.add('is-resizing'); resizer.classList.add('is-resizing'); document.body.style.cursor = 'col-resize'; e.preventDefault(); });
    document.addEventListener('mousemove', e => { if (!isResizing) return; const w = document.body.offsetWidth - e.clientX; if (w > 300 && w < 800) inspectorCol.style.setProperty('--inspector-width', `${w}px`); });
    document.addEventListener('mouseup', () => { if (isResizing) { isResizing = false; inspectorCol.classList.remove('is-resizing'); resizer.classList.remove('is-resizing'); document.body.style.cursor = ''; } });
}

// --- Tree scroll fade ---
const stepsScrollCol = document.getElementById('stepsScrollCol');
const treeScrollFade = document.getElementById('treeScrollFade');
function updateTreeFade() {
    if (!stepsScrollCol || !treeScrollFade) return;
    const atBottom = stepsScrollCol.scrollTop + stepsScrollCol.clientHeight >= stepsScrollCol.scrollHeight - 4;
    treeScrollFade.style.opacity = atBottom ? '0' : '1';
}
if (stepsScrollCol) { stepsScrollCol.addEventListener('scroll', updateTreeFade); }

// --- Drag-to-Resize Tree Column ---
const treeResizer = document.getElementById('treeResizer');
const treeCol = document.getElementById('treeCol');
let isTreeResizing = false;
if (treeResizer && treeCol) {
    treeResizer.addEventListener('mousedown', e => { isTreeResizing = true; treeCol.classList.add('is-resizing'); treeResizer.classList.add('is-resizing'); document.body.style.cursor = 'col-resize'; e.preventDefault(); });
    document.addEventListener('mousemove', e => { if (!isTreeResizing) return; const sidebar = document.getElementById('sidebar'); const sidebarW = sidebar ? sidebar.offsetWidth : 0; const w = e.clientX - sidebarW; if (w > 200 && w < 600) treeCol.style.setProperty('--tree-width', `${w}px`); });
    document.addEventListener('mouseup', () => { if (isTreeResizing) { isTreeResizing = false; treeCol.classList.remove('is-resizing'); treeResizer.classList.remove('is-resizing'); document.body.style.cursor = ''; } });
}

// --- Data helpers ---

function getStepChildren(parentIdStr) {
    return projectData.steps.filter(s => s.parent_id_str === parentIdStr).sort((a, b) => {
        const aParts = a.id_str.split('.').map(Number);
        const bParts = b.id_str.split('.').map(Number);
        for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
            if ((aParts[i]||0) !== (bParts[i]||0)) return (aParts[i]||0) - (bParts[i]||0);
        }
        return 0;
    });
}
function findStepById(idStr) { return projectData ? projectData.steps.find(s => s.id_str === idStr) : null; }

// --- Load project ---
async function loadProject() {
    try {
        const res = await fetch(LOAD_API_URL + PROJECT_UUID);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        projectData = await res.json();

        // Ensure all steps have deviations array
        projectData.steps.forEach(s => { if (!s.deviations) s.deviations = []; });

        // Update header
        document.getElementById('projectHeaderLoading').classList.add('hidden');
        document.getElementById('projectHeader').classList.remove('hidden');
        document.getElementById('headerProjectName').textContent = projectData.name || `${projectData.project_code} / SN ${projectData.engine_serial}`;
        document.getElementById('headerProjectMeta').textContent = `SN: ${projectData.engine_serial || 'N/A'} | ${projectData.engine_type || 'N/A'}`;
        if (projectData.uuid) {
            document.getElementById('reportPageLink').href = `/project/${projectData.uuid}/report`;
        }
        recalcProgress();
        updateLaufkartenBtnLabel();

        // Load comments
        await loadComments();

        // Select first main step that is not completed
        const firstSub = projectData.steps.find(s => (s.parent_id_str === null || s.parent_id_str === undefined) && s.status !== 'Completed');
        if (firstSub) selectStep(firstSub.id_str);
        else {
            document.getElementById('stepsScrollCol').innerHTML = '';
            renderTree();
            document.getElementById('emptyState').classList.remove('hidden');
        }
        renderTree();
    } catch(e) {
        document.getElementById('stepsScrollCol').innerHTML = `<p class="text-red-500 text-sm p-4">Fehler beim Laden: ${e.message}</p>`;
    }
}

function updateGlobalProgress() {
    const pct = projectData.progress_percent || 0;
    document.getElementById('headerProgressPct').textContent = `${pct}%`;
    document.getElementById('headerProgressBar').style.width = `${pct}%`;
}

function recalcProgress() {
    // Nur den aktuellen Zyklus für den Fortschritt zählen — N/R aus dem Nenner herausnehmen.
    const maxCycle = projectData.steps.reduce((m, s) => Math.max(m, s.cycle_number || 1), 1);
    const all = projectData.steps.filter(s => (s.cycle_number || 1) === maxCycle);
    const relevant = all.filter(s => s.status !== 'N/R');
    if (!relevant.length) { projectData.progress_percent = all.length ? 100 : 0; updateGlobalProgress(); return; }
    const weight = 100 / relevant.length;
    let calc = 0;
    relevant.forEach(s => {
        if (s.status === 'Completed') calc += weight;
        else if (s.status === 'In Progress' || s.status === 'On Hold') calc += weight * 0.5;
    });
    let pct = Math.round(calc);
    if (pct >= 100 && !relevant.every(s => s.status === 'Completed')) pct = 99;
    projectData.progress_percent = pct;
    updateGlobalProgress();
}

// --- Tree rendering ---
function hasOpenDeviationsInSubtree(stepIdStr) {
    const children = getStepChildren(stepIdStr);
    for (const child of children) {
        if ((child.deviations || []).some(d => d.status === 'Open')) return true;
        if (hasOpenDeviationsInSubtree(child.id_str)) return true;
    }
    return false;
}

function renderTree() {
    if (!projectData) return;
    const container = document.getElementById('stepsScrollCol');
    container.innerHTML = '';

    const maxCycle = projectData.steps.reduce((m, s) => Math.max(m, s.cycle_number || 1), 1);

    function renderStepRecursive(step, level, isLastInLevel, isOldCycle) {
        const isSelected = step.id_str === currentSelectedStepIdStr;
        const indentMargin = level * 14;
        const children = getStepChildren(step.id_str);
        const hasChildren = children.length > 0;

        let circleColor = 'bg-slate-200 text-slate-500';
        let lineColorBottom = 'bg-slate-200';
        let lineColorTop = 'bg-slate-200';
        let stepNameClass = 'text-slate-700';
        let statusTextColor = 'text-slate-400';
        let statusText = 'Anstehend';
        let chipIcon = 'radio_button_unchecked';
        let chipClass = 'bg-slate-100 text-slate-500 border-slate-200';

        if (step.status === 'Completed') { circleColor = 'bg-green-500 text-white'; lineColorBottom = 'bg-green-500'; statusText = 'Abgeschlossen'; statusTextColor = 'text-green-600'; chipIcon = 'check_circle'; chipClass = 'bg-green-100 text-green-700 border-green-200'; }
        else if (step.status === 'In Progress') { circleColor = 'bg-blue-600 text-white ring-2 ring-blue-200'; statusText = 'In Bearbeitung'; statusTextColor = 'text-blue-600'; chipIcon = 'play_circle'; chipClass = 'bg-blue-100 text-blue-700 border-blue-200'; }
        else if (step.status === 'On Hold') { circleColor = 'bg-yellow-500 text-yellow-950'; statusText = 'Pausiert'; statusTextColor = 'text-yellow-600'; chipIcon = 'pause_circle'; chipClass = 'bg-yellow-100 text-yellow-700 border-yellow-200'; }
        else if (step.status === 'N/R') { circleColor = 'bg-slate-200 text-slate-300'; stepNameClass = 'text-slate-400 line-through'; statusText = 'N/R'; chipIcon = 'block'; chipClass = 'bg-transparent text-slate-300 border-slate-200'; }

        if (level > 0) {
            const parent = findStepById(step.parent_id_str);
            if (parent && parent.status === 'Completed') lineColorTop = 'bg-green-500';
        }

        const openDevCount = (step.deviations || []).filter(d => d.status === 'Open').length;
        if (openDevCount > 0 && step.status !== 'Completed') circleColor = 'bg-red-500 text-white ring-2 ring-red-200';

        const devBadgeHtml = openDevCount > 0
            ? `<span class="inline-flex items-center bg-red-100 text-red-700 text-[9px] font-bold px-1 py-0.5 rounded ml-1"><span class="material-symbols-outlined text-[10px]">warning</span></span>`
            : '';

        let expandIcon = '<div class="w-5 flex-shrink-0"></div>';
        if (hasChildren) {
            const rotation = step.is_expanded !== false ? 'rotate-0' : '-rotate-90';
            expandIcon = `<button type="button" class="w-5 flex-shrink-0 flex items-center justify-center text-slate-400 hover:text-slate-600" onclick="event.stopPropagation();toggleStepExpansion('${step.id_str}')"><span class="material-symbols-outlined expand-toggle-btn ${rotation}" style="font-size:16px;">expand_more</span></button>`;
        }

        let downloadIconHtml = '';
        if (step.has_document) {
            const projectIdForUrl = `${projectData.project_code}_${projectData.engine_serial}`;
            const downloadLink = `{{ url_for('download_document_route', project_id='PROJECT_ID_PLACEHOLDER', step_name='STEP_NAME_PLACEHOLDER') }}`.replace('PROJECT_ID_PLACEHOLDER', encodeURIComponent(projectIdForUrl)).replace('STEP_NAME_PLACEHOLDER', encodeURIComponent(step.name));
            downloadIconHtml = `<a href="${downloadLink}" class="text-slate-400 hover:text-blue-600 p-1 rounded-full transition-colors flex-shrink-0" title="Dokument herunterladen" onclick="event.stopPropagation();"><span class="material-symbols-outlined" style="font-size:18px;">download</span></a>`;
        }

        let statusTextHtml = `<p class="text-[10px] font-bold ${statusTextColor} uppercase tracking-wider mt-0.5">${statusText}</p>`;
        if ((step.status === 'Completed' || step.status === 'N/R') && step.completed_by) {
            const dateStr = step.completed_at ? ` · ${step.completed_at}` : '';
            statusTextHtml = `<div class="mt-0.5 flex flex-col"><span class="text-[10px] font-bold ${statusTextColor} uppercase tracking-wider">${statusText}</span><span class="text-[10px] text-slate-400 mt-0.5">${step.completed_by}${dateStr}</span></div>`;
        }

        const forkedFromHtml = step.copied_from_global
            ? `<span class="text-[10px] text-slate-400" title="Status übernommen von #${step.copied_from_global}">↩ #${step.copied_from_global}</span>`
            : '';

        const closedDevCount = (step.deviations || []).filter(d => d.status === 'Closed').length;
        const closedDevDotHtml = (openDevCount === 0 && closedDevCount > 0)
            ? `<span class="w-1.5 h-1.5 rounded-full bg-green-400 flex-shrink-0 ml-1.5" title="Deviation gelöst"></span>`
            : '';

        const childDevHintHtml = (!step.parent_id_str && openDevCount === 0 && hasOpenDeviationsInSubtree(step.id_str))
            ? `<span class="inline-flex items-center gap-1 mt-0.5"><span class="w-1.5 h-1.5 rounded-full bg-red-400 flex-shrink-0"></span><span class="text-[9px] text-slate-400">Offene Deviation</span></span>`
            : '';

        const chipDisabled = isOldCycle ? 'pointer-events-none opacity-60' : '';
        const chipBtn = `<button type="button" class="step-status-chip ${chipClass} ${chipDisabled}" onclick="event.stopPropagation(); openStatusDropdown('${step.id_str}', event)" title="Status ändern"><span class="material-symbols-outlined" style="font-size:14px; font-variation-settings:'FILL' 1;">${chipIcon}</span>${isOldCycle ? '' : '<span class="material-symbols-outlined" style="font-size:16px;">arrow_drop_down</span>'}</button>`;

        const circleLabel = step.id_str;

        let html = `<div class="project-step-grid ${isSelected ? 'selected-step' : ''}" style="margin-left:${indentMargin}px;" onclick="selectStep('${step.id_str}')">
            <div class="project-step-icon-col">
                ${level > 0 ? `<div class="project-step-icon-connector ${lineColorTop} h-2"></div>` : '<div class="h-2"></div>'}
                <div class="project-step-icon-circle ${circleColor}" style="font-size:0.6rem;">${circleLabel}</div>
                ${(!isLastInLevel || (hasChildren && step.is_expanded !== false)) ? `<div class="project-step-icon-connector ${lineColorBottom}"></div>` : ''}
            </div>
            <div class="py-0.5 min-w-0 flex justify-between items-center">
                <div class="flex-1 pr-1 min-w-0">
                    <div class="flex items-center"><p class="text-[13px] font-medium leading-tight ${isSelected ? 'text-blue-800 font-semibold' : stepNameClass} truncate">${step.name}</p>${devBadgeHtml}${closedDevDotHtml}${forkedFromHtml}</div>
                    ${statusTextHtml}
                    ${childDevHintHtml}
                </div>
                <div class="flex items-center gap-0.5 flex-shrink-0">${downloadIconHtml}${expandIcon}${chipBtn}</div>
            </div>
        </div>`;

        if (step.is_expanded !== false && hasChildren) {
            children.forEach((child, idx) => { html += renderStepRecursive(child, level + 1, idx === children.length - 1, isOldCycle); });
        }
        return html;
    }

    const allMainSteps = projectData.steps
        .filter(s => !s.parent_id_str)
        .sort((a, b) => (a.global_step_number != null ? a.global_step_number : parseFloat(a.id_str)) - (b.global_step_number != null ? b.global_step_number : parseFloat(b.id_str)));

    const cycleGroups = {};
    allMainSteps.forEach(s => {
        const cn = s.cycle_number || 1;
        if (!cycleGroups[cn]) cycleGroups[cn] = [];
        cycleGroups[cn].push(s);
    });

    const multiCycle = maxCycle > 1;
    let html = '';
    for (let cn = 1; cn <= maxCycle; cn++) {
        const steps = cycleGroups[cn] || [];
        const isOld = cn < maxCycle;
        const collapsedClass = (multiCycle && isOld) ? ' cycle-collapsed' : '';

        html += `<div class="cycle-group ${isOld ? 'cycle-old' : 'cycle-current'}${collapsedClass}" data-cycle="${cn}">`;
        if (multiCycle) {
            html += `<div class="cycle-header" onclick="toggleCycleGroup(this.closest('.cycle-group'))">
                <span class="cycle-divider-line"></span>
                <span class="cycle-label">Zyklus ${cn}</span>
                <span class="material-symbols-outlined cycle-toggle-icon">expand_more</span>
                <span class="cycle-divider-line"></span>
            </div>`;
        }
        html += `<div class="cycle-body">`;
        steps.forEach((ms, idx) => { html += renderStepRecursive(ms, 0, idx === steps.length - 1, isOld); });
        html += `</div>`;
        html += `</div>`;
    }

    // Prüfen ob neuester Zyklus noch unberührt ist (nur nicht-kopierte Schritte mit last_updated_by == null)
    const newCycleSteps = projectData.steps.filter(s => (s.cycle_number || 1) === maxCycle && !s.copied_from_global);
    const cycleIsClean = newCycleSteps.every(s => s.status === 'Upcoming');
    const canDeleteCycle = maxCycle > 1 && cycleIsClean;
    const deleteTitle = maxCycle <= 1 ? 'Erster Zyklus kann nicht gelöscht werden' : (!cycleIsClean ? 'Zyklus enthält bereits bearbeitete Schritte' : `Zyklus ${maxCycle} löschen`);

    html += `<div style="display:flex;gap:8px;margin-top:12px;">`;
    html += `<button class="new-cycle-btn" style="flex:1;margin-top:0;" onclick="openNewCycleDialog()"><span class="material-symbols-outlined" style="font-size:16px;">add_circle</span> Neuer Zyklus</button>`;
    if (maxCycle > 1) {
        const disabledAttr = canDeleteCycle ? '' : 'disabled';
        const disabledStyle = canDeleteCycle ? '' : 'opacity:0.4;cursor:not-allowed;';
        html += `<button class="new-cycle-btn" style="flex:0 0 36px;width:36px;min-width:36px;padding:0;margin-top:0;border-color:#fca5a5;color:#dc2626;${disabledStyle}" ${disabledAttr} title="${deleteTitle}" onclick="confirmDeleteCycle()"><span class="material-symbols-outlined" style="font-size:16px;">delete</span></button>`;
    }
    html += `</div>`;

    container.innerHTML = html;

    if (maxCycle > 1) {
        const currentCycleEl = container.querySelector(`.cycle-group[data-cycle="${maxCycle}"]`);
        if (currentCycleEl) currentCycleEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }

    updateTreeFade();
}

function toggleStepExpansion(stepIdStr) {
    const step = findStepById(stepIdStr);
    if (step) { step.is_expanded = (step.is_expanded === false) ? true : false; renderTree(); }
}

function toggleCycleGroup(el) {
    if (el) el.classList.toggle('cycle-collapsed');
}

async function saveLaufkarteNumber(cycle, lkId, value) {
    if (!projectData.laufkarten) projectData.laufkarten = {};
    if (!projectData.laufkarten[String(cycle)]) projectData.laufkarten[String(cycle)] = {};
    projectData.laufkarten[String(cycle)][lkId] = value;
    try {
        await fetch(`/api/project_by_uuid/${PROJECT_UUID}/laufkarten`, {
            method: 'PATCH',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ cycle, lk_id: lkId, number: value })
        });
    } catch(e) { console.error('Fehler beim Speichern der Laufkarte:', e); }
    updateLaufkartenBtnLabel();
}

let laufkartenPanelOpen = false;

function toggleLaufkartenPanel() {
    laufkartenPanelOpen = !laufkartenPanelOpen;
    const panel = document.getElementById('laufkartenPanel');
    if (laufkartenPanelOpen) {
        if (commentsOpen) { commentsOpen = false; document.getElementById('commentsPanel').classList.remove('open'); }
        panel.classList.add('open');
        renderLaufkartenPanel();
    } else {
        panel.classList.remove('open');
    }
}

function renderLaufkartenPanel() {
    const lkTemplate = projectData.laufkarten_template || [];
    const lkData = projectData.laufkarten || {};
    const maxCycle = projectData.steps.reduce((m, s) => Math.max(m, s.cycle_number || 1), 1);
    const el = document.getElementById('laufkartenContent');
    if (!lkTemplate.length) { el.innerHTML = '<p class="text-xs text-slate-400 italic">Keine Laufkarten im Template definiert.</p>'; return; }

    let html = '';
    for (let cn = 1; cn <= maxCycle; cn++) {
        const cycleNums = lkData[String(cn)] || {};
        if (maxCycle > 1) html += `<div class="lk-panel-cycle">Zyklus ${cn}</div>`;
        html += `<div class="lk-panel-box">`;
        lkTemplate.forEach(lk => {
            const val = (cycleNums[lk.id] || '').replace(/"/g, '&quot;');
            html += `<div class="lk-panel-row">
                <span class="lk-panel-label">${lk.name.replace(/</g, '&lt;')}</span>
                <input class="lk-panel-input" type="text" inputmode="numeric" placeholder="—"
                    value="${val}"
                    onchange="saveLaufkarteNumber(${cn},'${lk.id}',this.value)"
                    onkeydown="if(event.key==='Enter')this.blur()">
            </div>`;
        });
        html += `</div>`;
    }
    el.innerHTML = html;
}

function updateLaufkartenBtnLabel() {
    const lkTemplate = projectData ? (projectData.laufkarten_template || []) : [];
    const lkData = projectData ? (projectData.laufkarten || {}) : {};
    const maxCycle = projectData ? projectData.steps.reduce((m, s) => Math.max(m, s.cycle_number || 1), 1) : 1;
    if (!lkTemplate.length) return;
    let filled = 0;
    for (let cn = 1; cn <= maxCycle; cn++) {
        const nums = lkData[String(cn)] || {};
        filled += lkTemplate.filter(lk => (nums[lk.id] || '').trim()).length;
    }
    const total = lkTemplate.length * maxCycle;
    const label = document.getElementById('laufkartenBtnLabel');
    if (label) label.textContent = filled > 0 ? `Laufkarten (${filled})` : 'Laufkarten';
    const btn = document.getElementById('laufkartenBtn');
    if (btn) btn.classList.toggle('hidden', lkTemplate.length === 0);
    if (laufkartenPanelOpen) renderLaufkartenPanel();
}

// --- Neue Zyklus Dialog ---
function openNewCycleDialog() {
    const maxCycle = projectData.steps.reduce((m, s) => Math.max(m, s.cycle_number || 1), 1);
    const currentSteps = projectData.steps.filter(s => !s.parent_id_str && (s.cycle_number || 1) === maxCycle);
    const openSteps = currentSteps.filter(s => s.status === 'Upcoming');
    const copyableSteps = currentSteps.filter(s => s.status !== 'Upcoming' && s.status !== 'N/R');

    let warningHtml = '';
    if (openSteps.length > 0) {
        const names = openSteps.map(s => `<strong>${s.name}</strong>`).join(', ');
        warningHtml = `<div class="dlg-warning"><span class="material-symbols-outlined" style="font-size:14px;vertical-align:-2px;">warning</span> ${openSteps.length} Schritt(e) werden als <strong>N/R</strong> markiert: ${names}</div>`;
    }

    const statusLabel = { 'Completed': 'Fertig', 'In Progress': 'Aktiv', 'On Hold': 'Pausiert' };
    const statusColor = { 'Completed': 'text-green-600', 'In Progress': 'text-blue-600', 'On Hold': 'text-yellow-600' };

    let copyHtml = '';
    if (copyableSteps.length > 0) {
        copyHtml = `<p class="text-[12px] text-slate-600 font-semibold mb-2">Status übernehmen aus Zyklus ${maxCycle}:</p><div class="dlg-copy-list">`;
        copyableSteps.forEach(s => {
            const lbl = statusLabel[s.status] || s.status;
            const col = statusColor[s.status] || 'text-slate-500';
            const children = projectData.steps.filter(c => c.parent_id_str === s.id_str && (c.cycle_number || 1) === maxCycle);
            const childHint = children.length > 0 ? `<span class="text-[10px] text-slate-400 ml-1">(+${children.length} Kinder)</span>` : '';
            copyHtml += `<label><input type="checkbox" name="copy_step" value="${s.id_str}" checked><span class="text-slate-700">${s.id_str} ${s.name}</span>${childHint}<span class="ml-auto text-[10px] font-semibold ${col}">${lbl}</span></label>`;
        });
        copyHtml += `</div>`;
    } else {
        copyHtml = `<p class="text-[12px] text-slate-500 mb-4">Keine begonnenen Schritte zum Übernehmen vorhanden.</p>`;
    }

    const dlgHtml = `<div id="newCycleDialog">
        <div class="dlg-box">
            <h3 class="text-slate-900 font-bold text-base mb-3">Zyklus ${maxCycle + 1} starten</h3>
            ${warningHtml}
            ${copyHtml}
            <div class="dlg-actions">
                <button type="button" onclick="document.getElementById('newCycleDialog').remove()" class="px-4 py-2 rounded-lg border border-slate-200 text-slate-600 text-sm font-medium hover:bg-slate-50">Abbrechen</button>
                <button type="button" onclick="confirmNewCycle()" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-semibold hover:bg-blue-700">Zyklus erstellen</button>
            </div>
        </div>
    </div>`;
    document.body.insertAdjacentHTML('beforeend', dlgHtml);
}

async function confirmNewCycle() {
    const checkedBoxes = document.querySelectorAll('#newCycleDialog input[name="copy_step"]:checked');
    const copySteps = Array.from(checkedBoxes).map(cb => cb.value);
    document.getElementById('newCycleDialog').remove();

    try {
        const res = await fetch(`/api/project_by_uuid/${PROJECT_UUID}/new_cycle`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ copy_steps: copySteps })
        });
        if (!res.ok) { const e = await res.json(); alert('Fehler: ' + (e.error || 'Unbekannt')); return; }
        // Re-fetch via GET so template instructions are merged in for the new cycle steps
        const fullRes = await fetch(`/api/project_by_uuid/${PROJECT_UUID}`);
        const updated = await fullRes.json();
        const lkTemplate = projectData.laufkarten_template;
        projectData = updated;
        projectData.laufkarten_template = lkTemplate;
        currentSelectedStepIdStr = null;
        recalcProgress();
        updateLaufkartenBtnLabel();
        renderTree();
        renderMainArea();
    } catch (err) {
        console.error('Zyklus-Fehler:', err);
        alert('Netzwerkfehler beim Erstellen des Zyklus.');
    }
}

async function confirmDeleteCycle() {
    const maxCycle = projectData.steps.reduce((m, s) => Math.max(m, s.cycle_number || 1), 1);
    if (!confirm(`Zyklus ${maxCycle} wirklich löschen?\n\nAlle Schritte dieses Zyklus werden entfernt und die vorherigen N/R-Markierungen rückgängig gemacht.`)) return;
    try {
        const res = await fetch(`/api/project_by_uuid/${PROJECT_UUID}/delete_cycle`, { method: 'DELETE' });
        if (!res.ok) { const e = await res.json(); alert('Fehler: ' + (e.error || 'Unbekannt')); return; }
        const updated = await res.json();
        const lkTemplate = projectData.laufkarten_template;
        projectData = updated;
        projectData.laufkarten_template = lkTemplate;
        currentSelectedStepIdStr = null;
        recalcProgress();
        updateLaufkartenBtnLabel();
        renderTree();
        renderMainArea();
    } catch (err) {
        console.error('Zyklus-Lösch-Fehler:', err);
        alert('Netzwerkfehler beim Löschen des Zyklus.');
    }
}

// --- Step selection & main area ---
function selectStep(stepIdStr) {
    currentSelectedStepIdStr = stepIdStr;
    renderTree();
    renderMainArea();
}

function renderMainArea() {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step) return;
    const isChapter = !step.parent_id_str;

    document.getElementById('emptyState').classList.add('hidden');

    if (isChapter) {
        document.getElementById('taskState').classList.add('hidden');
        document.getElementById('chapterState').classList.remove('hidden');

        // Inspector auch für Kapitel zeigen, wenn Deviations vorhanden
        const openDevCount = (step.deviations || []).filter(d => d.status === 'Open').length;
        const iBadge = document.getElementById('inspectorBadge');
        const toggleBtn = document.getElementById('toggleInspectorBtn');
        if ((step.deviations || []).length > 0) {
            toggleBtn.classList.remove('hidden');
            showInspectorList();
            setInspectorOpen(true);
            if (openDevCount > 0) {
                iBadge.textContent = openDevCount; iBadge.classList.remove('hidden');
            } else {
                iBadge.classList.add('hidden');
            }
            renderDeviations(step);
        } else {
            toggleBtn.classList.add('hidden');
            setInspectorOpen(false);
        }
    } else {
        document.getElementById('chapterState').classList.add('hidden');
        document.getElementById('taskState').classList.remove('hidden');
        document.getElementById('toggleInspectorBtn').classList.remove('hidden');

        const openDevCount = (step.deviations || []).filter(d => d.status === 'Open').length;
        const iBadge = document.getElementById('inspectorBadge');
        showInspectorList();
        const totalDevCount = (step.deviations || []).length;
        if (totalDevCount > 0) {
            setInspectorOpen(true);
            if (openDevCount > 0) {
                iBadge.textContent = openDevCount; iBadge.classList.remove('hidden');
            } else {
                iBadge.classList.add('hidden');
            }
        } else {
            iBadge.classList.add('hidden'); setInspectorOpen(false);
        }

        renderConsumables(step);
        renderReferences(step);
        renderChecklist(step);
        renderDeviations(step);
    }
}

// --- Status Chip Dropdown ---
let _activeDropdownCleanup = null;

function openStatusDropdown(stepIdStr, event) {
    const rect = event.currentTarget.getBoundingClientRect();
    selectStep(stepIdStr);
    const step = findStepById(stepIdStr);
    if (!step) return;

    closeStatusDropdown();

    let items = '';
    if (step.status === 'Upcoming') {
        items += `<button onclick="closeStatusDropdown(); changeStatus('In Progress')"><span class="material-symbols-outlined text-[16px] text-blue-600">play_circle</span> Starten</button>`;
        items += `<hr><button onclick="closeStatusDropdown(); markStepAsNR()"><span class="material-symbols-outlined text-[16px] text-slate-400">block</span> Als N/R markieren</button>`;
    } else if (step.status === 'In Progress') {
        items += `<button onclick="closeStatusDropdown(); completeCurrentStepWithCheck()"><span class="material-symbols-outlined text-[16px] text-green-600">task_alt</span> Abschließen</button>`;
        items += `<hr>`;
        items += `<button onclick="closeStatusDropdown(); changeStatus('On Hold')"><span class="material-symbols-outlined text-[16px] text-yellow-600">pause_circle</span> Pausieren</button>`;
        items += `<button onclick="closeStatusDropdown(); openDeviationForm()"><span class="material-symbols-outlined text-[16px] text-orange-500">add_alert</span> Deviation melden</button>`;
        items += `<hr><button onclick="closeStatusDropdown(); changeStatus('Upcoming')"><span class="material-symbols-outlined text-[16px] text-slate-400">undo</span> Zurücksetzen</button>`;
    } else if (step.status === 'On Hold') {
        items += `<button onclick="closeStatusDropdown(); changeStatus('In Progress')"><span class="material-symbols-outlined text-[16px] text-blue-600">play_circle</span> Fortsetzen</button>`;
        items += `<button onclick="closeStatusDropdown(); openDeviationForm()"><span class="material-symbols-outlined text-[16px] text-orange-500">add_alert</span> Deviation melden</button>`;
        items += `<hr><button onclick="closeStatusDropdown(); reopenStep()"><span class="material-symbols-outlined text-[16px] text-slate-400">undo</span> Zurücksetzen</button>`;
    } else if (step.status === 'Completed' || step.status === 'N/R') {
        items += `<button onclick="closeStatusDropdown(); reopenStep()"><span class="material-symbols-outlined text-[16px] text-slate-400">undo</span> Wiedereröffnen</button>`;
    }

    const dropdown = document.createElement('div');
    dropdown.id = 'statusDropdown';
    dropdown.innerHTML = items;
    document.body.appendChild(dropdown);

    const btnCount = dropdown.querySelectorAll('button').length;
    const dropH = btnCount * 36 + 16;
    const spaceBelow = window.innerHeight - rect.bottom;
    dropdown.style.top = (spaceBelow > dropH ? rect.bottom + 4 : rect.top - dropH - 4) + 'px';
    let left = rect.left;
    if (left + 180 > window.innerWidth) left = window.innerWidth - 184;
    dropdown.style.left = left + 'px';

    _activeDropdownCleanup = (e) => { if (!dropdown.contains(e.target)) closeStatusDropdown(); };
    setTimeout(() => document.addEventListener('click', _activeDropdownCleanup), 0);
}

function closeStatusDropdown() {
    const d = document.getElementById('statusDropdown');
    if (d) d.remove();
    if (_activeDropdownCleanup) { document.removeEventListener('click', _activeDropdownCleanup); _activeDropdownCleanup = null; }
}

function toggleDevDetails(devId) {
    const p = document.getElementById('devdet-' + devId);
    const btn = document.getElementById('devdet-btn-' + devId);
    if (!p || !btn) return;
    const isExpanded = p.style.webkitLineClamp === 'unset';
    p.style.webkitLineClamp = isExpanded ? '2' : 'unset';
    p.style.webkitBoxOrient = isExpanded ? 'vertical' : '';
    p.style.overflow = isExpanded ? 'hidden' : 'visible';
    p.style.display = isExpanded ? '-webkit-box' : 'block';
    btn.textContent = isExpanded ? '... mehr' : 'weniger';
}

// --- Checklist Action Header ---
function buildChecklistHeader(step, checkedCount, totalCount) {
    const statusStyles = {
        'Upcoming':    ['bg-slate-100 text-slate-600 border-slate-200', 'Anstehend'],
        'In Progress': ['bg-blue-100 text-blue-700 border-blue-200',   'In Bearbeitung'],
        'Completed':   ['bg-green-100 text-green-700 border-green-200', 'Abgeschlossen'],
        'On Hold':     ['bg-yellow-100 text-yellow-700 border-yellow-200', 'Pausiert'],
        'N/R':         ['bg-slate-200 text-slate-500 border-slate-300', 'N/R'],
    };
    const [chipCls, chipLabel] = statusStyles[step.status] || statusStyles['Upcoming'];
    const allChecked = totalCount === 0 || checkedCount >= totalCount;

    let actionHtml = '';
    if (step.status === 'Completed') {
        actionHtml = `<div class="flex items-center gap-2">
            <span class="text-xs text-green-700 font-medium flex items-center gap-1"><span class="material-symbols-outlined text-[14px]">check_circle</span>${step.completed_by||'System'}</span>
            <button onclick="reopenStep()" class="px-3 py-1.5 text-xs font-semibold text-slate-600 bg-white border border-slate-300 hover:bg-slate-50 rounded-lg flex items-center gap-1 shadow-sm"><span class="material-symbols-outlined text-[13px]">undo</span> Wiedereröffnen</button>
        </div>`;
    } else if (step.status === 'N/R') {
        actionHtml = `<div class="flex items-center gap-2">
            <span class="text-xs text-slate-500 font-medium flex items-center gap-1"><span class="material-symbols-outlined text-[14px]">block</span>${step.completed_by||'System'}</span>
            <button onclick="reopenStep()" class="px-3 py-1.5 text-xs font-semibold text-slate-600 bg-white border border-slate-300 hover:bg-slate-50 rounded-lg flex items-center gap-1 shadow-sm"><span class="material-symbols-outlined text-[13px]">undo</span> Wiedereröffnen</button>
        </div>`;
    } else if (step.status === 'In Progress') {
        const countBadge = totalCount > 0 ? `<span class="text-xs text-slate-400 font-mono">${checkedCount}/${totalCount}</span>` : '';
        if (allChecked) {
            actionHtml = `<div class="flex items-center gap-2">${countBadge}<button onclick="completeStep()" class="px-4 py-1.5 text-sm font-bold text-white bg-green-600 hover:bg-green-500 rounded-lg flex items-center gap-1.5 shadow-[0_2px_12px_rgba(22,163,74,0.35)] hover:-translate-y-0.5 transition-all ring-2 ring-green-100"><span class="material-symbols-outlined text-[16px]">task_alt</span> Abschließen</button></div>`;
        } else {
            actionHtml = `<div class="flex items-center gap-2">${countBadge}</div>`;
        }
    } else {
        actionHtml = `<span class="text-xs text-slate-400 italic">Im Baum starten</span>`;
    }

    return `<div id="checklistActionHeader" class="px-4 py-2.5 border-b border-slate-200 bg-white flex items-center justify-end">
        ${actionHtml}
    </div>`;
}

function updateChecklistHeader(step, checkedCount, totalCount) {
    const header = document.getElementById('checklistActionHeader');
    if (!header) return;
    const tmp = document.createElement('div');
    tmp.innerHTML = buildChecklistHeader(step, checkedCount, totalCount);
    header.replaceWith(tmp.firstElementChild);
}

// --- Status changes ---
function nowString() { return new Date().toLocaleString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}); }

function changeStatus(newStatus) {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step) return;
    if (newStatus === 'In Progress' && step.parent_id_str) {
        const parent = findStepById(step.parent_id_str);
        if (parent && parent.status !== 'In Progress' && parent.status !== 'Completed') {
            parent.status = 'In Progress';
        }
    }
    step.status = newStatus;
    recalcProgress();
    renderTree(); renderMainArea();
    saveProject();
}

function completeStep() {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step) return;
    step.status = 'Completed';
    step.completed_by = LOGGED_IN_USER;
    step.completed_at = nowString();
    recalcProgress();
    renderTree(); renderMainArea();
    saveProject();
}

function completeCurrentStepWithCheck() {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step) return;
    let openCount = 0, warningMsg = '';
    if (!step.parent_id_str) {
        function countOpenChildren(stepId) {
            return getStepChildren(stepId).reduce((n, c) =>
                n + (c.status !== 'Completed' && c.status !== 'N/R' ? 1 : 0) + countOpenChildren(c.id_str), 0);
        }
        openCount = countOpenChildren(step.id_str);
        warningMsg = `${openCount} Unterschritt${openCount > 1 ? 'e sind' : ' ist'} noch nicht abgeschlossen. Trotzdem abschließen?`;
    } else {
        openCount = (step.instructions || []).filter(i => !i.nr && !i.checked && i.status !== 'Completed').length;
        warningMsg = `${openCount} Checklistenpunkt${openCount > 1 ? 'e sind' : ' ist'} noch offen. Trotzdem abschließen?`;
    }
    if (openCount > 0 && !confirm(warningMsg)) return;
    completeStep();
}

function markStepAsNR() {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step) return;
    if (confirm(`"${step.name}" als Nicht benötigt (N/R) markieren?`)) {
        step.status = 'N/R';
        step.completed_by = LOGGED_IN_USER;
        step.completed_at = nowString();
        recalcProgress();
        renderTree(); renderMainArea();
        saveProject();
    }
}

function reopenStep() {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step) return;
    if (step.parent_id_str) {
        const parent = findStepById(step.parent_id_str);
        if (parent && parent.status !== 'In Progress' && parent.status !== 'Completed') {
            alert(`Kapitel "${parent.id_str} ${parent.name}" muss "In Bearbeitung" sein.`); return;
        }
    }
    if (confirm(`Schritt wiedereröffnen? Sign-Off von ${step.completed_by||'System'} wird ungültig.`)) {
        step.status = 'In Progress';
        step.completed_by = null; step.completed_at = null;
        recalcProgress();
        renderTree(); renderMainArea();
        saveProject();
    }
}

function shakeUnchecked() {
    document.querySelectorAll('.instruction-row:not(.is-checked)').forEach(r => { r.classList.remove('shake-error'); void r.offsetWidth; r.classList.add('shake-error'); });
}

// --- Checklist ---
function renderChecklist(step) {
    const container = document.getElementById('checklistContainer');
    const isDisabled = step.status !== 'In Progress';

    if (!step.instructions || !step.instructions.length) {
        container.innerHTML = buildChecklistHeader(step, 0, 0) + `<div class="text-center py-14 bg-white"><span class="material-symbols-outlined text-slate-300 text-5xl mb-3">fact_check</span><h3 class="text-lg font-bold text-slate-700">Statuspunkt</h3><p class="text-slate-500 text-sm mt-1">Kein Checklisten-Item definiert.</p></div>`;
        updateProgress(0,0); return;
    }

    // Pre-scan: assign sequential Fig. numbers across all instructions (per unique filename)
    const figMap = {};
    let figCounter = 1;
    step.instructions.forEach(i => {
        [...(i.text||'').matchAll(/\[\[IMAGE:(.*?)\]\]/g)].forEach(([, raw]) => {
            const filename = raw.split('|')[0].trim();
            if (!figMap[filename]) figMap[filename] = figCounter++;
        });
    });

    let html = '';
    step.instructions.forEach((inst, index) => {
        let textHtml = (inst.text||'').replace(/\\[\\[IMAGE:(.*?)\\]\\]/g, (m, raw) => {
            const parts = raw.split('|');
            const filename = parts[0].trim();
            const userLabel = parts[1] ? parts[1].trim() : '';
            const figNum = figMap[filename] || '?';
            const display = userLabel ? `Fig. ${figNum} – ${userLabel}` : `Fig. ${figNum}`;
            const safeDisplay = display.replace(/'/g, "\\'");
            return `<button type="button" onclick="openImagePanel('${filename}','${safeDisplay}',event)" class="inline-flex items-center gap-1 text-blue-700 bg-blue-50 px-2 py-0.5 rounded border border-blue-200 hover:bg-blue-100 font-semibold font-mono text-[11px] ml-1 shadow-sm">${display}</button>`;
        }).replace(/\\[\\[REF:(.*?)\\]\\]/g, (m, ref) =>
            `<span class="inline-flex items-center gap-1 text-violet-800 bg-violet-50 px-1.5 py-0.5 rounded border border-violet-200 font-mono font-bold text-[11px] ml-1 shadow-sm cursor-default" title="${ref}"><span class="material-symbols-outlined text-[12px] text-violet-500">library_books</span>${ref}</span>`
        ).replace(/\\((\\d+)\\)/g, (m, n) =>
            `<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-100 text-blue-700 text-[10px] font-bold border border-blue-200 mx-0.5">${n}</span>`
        );

        const isNR = !!inst.nr;
        let rowClass = 'bg-white', textClass = 'text-slate-800', checkHtml = '';
        if (inst.type === 'caution') { rowClass = 'bg-yellow-50/50'; textClass = 'text-yellow-900 font-medium'; }
        if (inst.type === 'warning') { rowClass = 'bg-red-50/50 border-l-4 border-red-500'; textClass = 'text-red-900 font-bold'; }
        if (isNR) { rowClass += ' is-nr'; }
        else if (isDisabled) rowClass += ' is-disabled';

        const isChecked = !isNR && (inst.status === 'Completed' || inst.checked);
        const isCheckedClass = isChecked ? 'is-checked' : (isNR ? 'is-nr' : '');

        let auditHtml = '';
        let auditClass = 'hidden';
        if (isChecked) {
            const who = inst.completed_by || (step.status === 'Completed' ? step.completed_by : 'System');
            const when = inst.completed_at || '';
            auditHtml = `<span class="material-symbols-outlined text-[10px]">check</span> ${who}${when ? ' ('+when+')' : ''}`;
            auditClass = 'flex';
        } else if (isNR) {
            const who = inst.nr_by || LOGGED_IN_USER;
            auditHtml = `<span class="material-symbols-outlined text-[10px]">block</span> N/R – ${who}`;
            auditClass = 'flex';
        }

        const nrBtnTitle = isNR ? 'N/R aufheben' : 'Als N/R markieren';
        const nrBtnIcon = isNR ? 'undo' : 'block';
        const nrBtnColor = isNR ? 'text-blue-400 hover:text-blue-600' : 'text-slate-300 hover:text-slate-500';
        const nrBtnHtml = (!isDisabled || isNR) ? `<button type="button" title="${nrBtnTitle}" class="instruction-nr-btn p-1 rounded hover:bg-slate-100 ${nrBtnColor}" data-inst-idx="${index}" onclick="event.preventDefault();event.stopPropagation();toggleInstNR(${index})"><span class="material-symbols-outlined text-[15px]">${nrBtnIcon}</span></button>` : '';

        let selectHtml = '';
        if (inst.type === 'part_selection') {
            const opts = (Array.isArray(inst.options) ? inst.options : []).map(o => typeof o === 'string' ? {label: o} : o);
            selectHtml = `<select class="form-select ml-3 border-slate-300 rounded-md text-sm cursor-pointer shadow-sm flex-shrink-0" style="min-width:160px;max-width:260px" ${isDisabled || isChecked || isNR ? 'disabled' : ''} onchange="this.closest('.instruction-row').querySelector('.instruction-checkbox').checked=!!this.value;this.closest('.instruction-row').querySelector('.instruction-checkbox').dispatchEvent(new Event('change'))"><option value="">— Auswahl —</option>${opts.map(o=>`<option value="${o.hw_uuid||o.label}" ${inst.selected_option===(o.hw_uuid||o.label)?'selected':''}>${o.label}</option>`).join('')}</select>`;
            checkHtml = `<input type="checkbox" class="instruction-checkbox w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer shadow-sm" ${isDisabled||isNR?'disabled':''} ${isChecked?'checked':''} ${isNR?'data-nr="true"':''} onclick="const sel=this.closest('.instruction-row').querySelector('select');if(!sel.value){event.preventDefault();const r=this.closest('.instruction-row');r.classList.add('shake-error');setTimeout(()=>r.classList.remove('shake-error'),600);}">`;
        } else if (inst.type === 'text_input') {
            const phAttr = (inst.placeholder || 'Eingabe...').replace(/"/g, '&quot;');
            const valAttr = (inst.text_value || '').replace(/"/g, '&quot;');
            selectHtml = `<input type="text" class="inst-text-value-input form-input ml-3 border-slate-300 rounded-md text-sm shadow-sm flex-shrink-0" style="min-width:160px;max-width:260px" placeholder="${phAttr}" value="${valAttr}" ${isDisabled || isChecked || isNR ? 'disabled' : ''}>`;
            checkHtml = `<input type="checkbox" class="instruction-checkbox w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer shadow-sm" ${isDisabled||isNR?'disabled':''} ${isChecked?'checked':''} ${isNR?'data-nr="true"':''} onclick="const inp=this.closest('.instruction-row').querySelector('.inst-text-value-input');if(inp&&!inp.value.trim()){event.preventDefault();const r=this.closest('.instruction-row');r.classList.add('shake-error');setTimeout(()=>r.classList.remove('shake-error'),600);}">`;
        } else {
            checkHtml = `<input type="checkbox" class="instruction-checkbox w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer shadow-sm" ${isDisabled||isNR?'disabled':''} ${isChecked?'checked':''} ${isNR?'data-nr="true"':''}>`;
        }

        html += `<label class="instruction-row flex items-start gap-4 p-4 cursor-pointer group ${rowClass} ${index<step.instructions.length-1?'border-b border-slate-200':''} ${isCheckedClass}">
            <div class="mt-0.5 flex-shrink-0">${checkHtml}</div>
            <div class="w-10 flex-shrink-0 pt-0.5"><span class="font-mono text-xs font-bold text-slate-500">${inst.step_num||String(index+1)}</span></div>
            <div class="flex-1 flex items-start">
                <div class="flex-1">
                    ${inst.type==='warning'?'<span class="material-symbols-outlined text-red-600 align-text-bottom mr-1 text-[18px]">gpp_maybe</span>':''}
                    <p class="instruction-text text-sm leading-relaxed ${textClass} inline ${isCheckedClass}">${textHtml}</p>
                    <div class="instruction-audit-trail mt-1 text-[10px] ${isNR ? 'text-slate-400' : 'text-green-600'} font-medium items-center gap-0.5 ${auditClass}">${auditHtml}</div>
                </div>
                ${selectHtml}
            </div>
            ${nrBtnHtml}
        </label>`;
    });
    const initialChecked = step.instructions.filter(i => !i.nr && (i.status === 'Completed' || i.checked)).length;
    const initialTotal = step.instructions.filter(i => !i.nr).length;
    container.innerHTML = buildChecklistHeader(step, initialChecked, initialTotal) + html;

    const checkboxes = document.querySelectorAll('.instruction-checkbox:not([data-nr])');
    checkboxes.forEach((cb, idx) => {
        cb.addEventListener('change', async function() {
            const row = this.closest('.instruction-row');
            row.classList.remove('shake-error');
            const text = row.querySelector('.instruction-text');
            const audit = row.querySelector('.instruction-audit-trail');
            const inst = step.instructions[idx];
            if (inst.type === 'part_selection') {
                const sel = row.querySelector('select');
                inst.selected_option = sel ? sel.value : null;
                inst.selected_label = sel && sel.selectedIndex > 0 ? sel.options[sel.selectedIndex].text : null;
            }
            if (inst.type === 'text_input') {
                const inp = row.querySelector('.inst-text-value-input');
                inst.text_value = inp ? inp.value.trim() : null;
            }
            const patch = {};
            if (this.checked) {
                row.classList.add('is-checked'); if(text) text.classList.add('is-checked');
                inst.checked = true; inst.completed_by = LOGGED_IN_USER; inst.completed_at = nowString();
                patch.checked = true; patch.completed_by = inst.completed_by; patch.completed_at = inst.completed_at;
                if (inst.type === 'part_selection') {
                    const sel = row.querySelector('select');
                    if (sel) sel.disabled = true;
                    patch.selected_option = inst.selected_option;
                    patch.selected_label = inst.selected_label;
                }
                if (inst.type === 'text_input') {
                    const inp = row.querySelector('.inst-text-value-input');
                    if (inp) inp.disabled = true;
                    patch.text_value = inst.text_value;
                }
                if (audit) { audit.innerHTML = `<span class="material-symbols-outlined text-[10px]">check</span> ${inst.completed_by} (${inst.completed_at})`; audit.classList.remove('hidden'); audit.classList.add('flex'); }
            } else {
                if (!confirm('Möchten Sie diesen Checklistenpunkt wirklich zurücksetzen?\\n\\nDieser Schritt wurde bereits abgehakt und als erledigt markiert.')) {
                    this.checked = true;
                    return;
                }
                row.classList.remove('is-checked'); if(text) text.classList.remove('is-checked');
                inst.checked = false; inst.completed_by = null; inst.completed_at = null;
                patch.checked = false; patch.completed_by = null; patch.completed_at = null;
                if (inst.type === 'part_selection') {
                    const sel = row.querySelector('select');
                    if (sel) sel.disabled = false;
                    patch.selected_option = inst.selected_option;
                    patch.selected_label = null;
                }
                if (inst.type === 'text_input') {
                    const inp = row.querySelector('.inst-text-value-input');
                    if (inp) inp.disabled = false;
                    patch.text_value = inst.text_value;
                }
                if (audit) { audit.innerHTML=''; audit.classList.add('hidden'); audit.classList.remove('flex'); }
            }
            const cCount = document.querySelectorAll('.instruction-checkbox:not([data-nr]):checked').length;
            updateProgress(cCount, checkboxes.length);
            updateChecklistHeader(findStepById(currentSelectedStepIdStr), cCount, checkboxes.length);

            // Atomic patch — only this one instruction, no full overwrite
            const projectId = `${projectData.project_code}_${projectData.engine_serial}`;
            try {
                await fetch(`${SAVE_API_URL}${encodeURIComponent(projectId)}/instruction`, {
                    method: 'PATCH',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({ step_id_str: step.id_str, inst_index: idx, patch })
                });
            } catch(e) { console.error('Patch-Fehler:', e); }
        });
    });

    const initial = document.querySelectorAll('.instruction-checkbox:not([data-nr]):checked').length;
    updateProgress(initial, checkboxes.length);
}

function updateProgress(checked, total) {}

async function toggleInstNR(idx) {
    const step = findStepById(currentSelectedStepIdStr);
    if (!step || !step.instructions) return;
    const inst = step.instructions[idx];
    if (!inst) return;
    const nowNR = !inst.nr;
    const patch = { nr: nowNR, nr_by: nowNR ? LOGGED_IN_USER : null, nr_at: nowNR ? nowString() : null };
    if (nowNR && (inst.checked || inst.status === 'Completed')) {
        patch.checked = false; patch.completed_by = null; patch.completed_at = null;
        inst.checked = false; inst.completed_by = null; inst.completed_at = null;
    }
    inst.nr = nowNR; inst.nr_by = patch.nr_by; inst.nr_at = patch.nr_at;
    const projectId = `${projectData.project_code}_${projectData.engine_serial}`;
    try {
        await fetch(`${SAVE_API_URL}${encodeURIComponent(projectId)}/instruction`, {
            method: 'PATCH',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ step_id_str: step.id_str, inst_index: idx, patch })
        });
    } catch(e) { console.error('NR-Patch-Fehler:', e); }
    renderChecklist(step);
}

function togglePanelBody(bodyId, chevronId) {
    const body = document.getElementById(bodyId);
    const chevron = document.getElementById(chevronId);
    if (!body) return;
    const isHidden = body.classList.toggle('hidden');
    if (chevron) chevron.style.transform = isHidden ? '' : 'rotate(180deg)';
}

// --- Consumables ---
function renderConsumables(step) {
    const container = document.getElementById('consumablesList');
    const itemsList = document.getElementById('consumablesListItems');
    const tools = step.tools && step.tools.length ? step.tools
        : (step.consumables || []).map(c => ({type: 'consumable', label: c.item || '', quantity: c.quantity || '', hw_uuid: null}));
    if (!tools.length) { container.classList.add('hidden'); return; }
    container.classList.remove('hidden');
    // body stays collapsed — user expands on demand
    const body = document.getElementById('consumablesBody');
    if (body) { body.classList.add('hidden'); const ch = document.getElementById('consumablesChevron'); if (ch) ch.style.transform = ''; }
    const countBadge = document.getElementById('consumablesCountBadge');
    if (countBadge) countBadge.textContent = tools.length + ' item' + (tools.length !== 1 ? 's' : '');
    const locked = step.status !== 'In Progress';
    if (body) { body.style.opacity = locked ? '0.5' : ''; body.style.filter = locked ? 'grayscale(100%)' : ''; body.style.pointerEvents = locked ? 'none' : ''; }
    itemsList.innerHTML = tools.map((t, idx) => {
        const isTooling = t.type === 'tooling';
        const icon = isTooling
            ? `<span class="material-symbols-outlined text-[15px] text-blue-500">build</span>`
            : `<span class="material-symbols-outlined text-[15px] text-amber-500">water_drop</span>`;
        const rawLabel = t.label || '';
        const m = rawLabel.match(/\((.+)\)/);
        const label = (isTooling && t.hw_uuid && m) ? m[1] : rawLabel;
        const qty = t.quantity ? `<span class="text-slate-400 text-xs ml-2">${t.quantity}</span>` : '';
        const labelEl = (isTooling && t.hw_uuid)
            ? `<span class="font-medium text-blue-700 flex-1 cursor-pointer hover:underline flex items-center gap-1"
                onclick="window.open('/test_equipment?hwid=${t.hw_uuid}','_blank')"
                title="Im Inventar öffnen">${label}<span class="material-symbols-outlined text-[11px] text-blue-400">open_in_new</span></span>`
            : `<span class="font-medium text-slate-700 flex-1">${label}</span>`;
        return `<li class="flex items-center gap-2.5 py-2">
            <span class="font-mono text-xs font-bold text-blue-600 w-6 flex-shrink-0 text-center">(${idx + 1})</span>
            ${icon}
            ${labelEl}
            ${qty}
        </li>`;
    }).join('');
}

// --- Applicable Documents (References) ---
function renderReferences(step) {
    const container = document.getElementById('referencesList');
    const tbody = document.getElementById('referencesTableBody');
    const countBadge = document.getElementById('referencesCountBadge');
    const refs = step.references || [];
    if (!refs.length) { container.classList.add('hidden'); return; }
    container.classList.remove('hidden');
    // body stays collapsed — user expands on demand
    const refsBody = document.getElementById('referencesBody');
    if (refsBody) { refsBody.classList.add('hidden'); const ch = document.getElementById('referencesChevron'); if (ch) ch.style.transform = ''; }
    countBadge.textContent = refs.length + ' doc' + (refs.length !== 1 ? 's' : '');

    const DOC_TYPE_STYLES = {
        'AMM':  'bg-blue-100 text-blue-800 border-blue-200',
        'IPC':  'bg-green-100 text-green-800 border-green-200',
        'SB':   'bg-orange-100 text-orange-800 border-orange-200',
        'ESM':  'bg-purple-100 text-purple-800 border-purple-200',
        'CMM':  'bg-teal-100 text-teal-800 border-teal-200',
        'EO':   'bg-rose-100 text-rose-800 border-rose-200',
        'QEC':  'bg-indigo-100 text-indigo-800 border-indigo-200',
    };

    tbody.innerHTML = refs.map((ref, idx) => {
        const type = (ref.doc_type || '').toUpperCase();
        const chipStyle = DOC_TYPE_STYLES[type] || 'bg-slate-100 text-slate-700 border-slate-200';
        const docNum = ref.doc_number || '—';
        const chapter = ref.chapter || '';
        const title = ref.title || '';
        const url = ref.url || '';

        const titleCell = url
            ? `<a href="${url}" target="_blank" rel="noopener" class="text-violet-700 hover:underline flex items-center gap-1 font-medium">${title}<span class="material-symbols-outlined text-[11px] text-violet-400">open_in_new</span></a>`
            : `<span class="text-slate-700 font-medium">${title}</span>`;

        return `<tr class="${idx % 2 === 0 ? 'bg-white' : 'bg-violet-50/20'} border-b border-violet-50 last:border-0">
            <td class="px-4 py-2">
                <span class="inline-flex items-center px-1.5 py-0.5 rounded border font-mono font-bold text-[10px] tracking-wider ${chipStyle}">${type || '—'}</span>
            </td>
            <td class="px-4 py-2 font-mono text-[11px] text-slate-600 font-semibold whitespace-nowrap">${docNum}</td>
            <td class="px-4 py-2 font-mono text-[11px] text-slate-500 whitespace-nowrap">${chapter}</td>
            <td class="px-4 py-2">${titleCell}</td>
        </tr>`;
    }).join('');
}

// --- Inspector ---
function setInspectorOpen(open) {
    const btn = document.getElementById('toggleInspectorBtn');
    if (open) {
        inspectorCol.classList.remove('collapsed');
        document.getElementById('inspectorToggleIcon').textContent = 'right_panel_open';
        btn.style.display = 'none';
    } else {
        inspectorCol.classList.add('collapsed');
        document.getElementById('inspectorToggleIcon').textContent = 'left_panel_open';
        setTimeout(() => { btn.style.display = ''; }, 200);
    }
}
function toggleInspector() {
    setInspectorOpen(inspectorCol.classList.contains('collapsed'));
}

function showInspectorList() {
    ['inspectorImageMode','inspectorFormMode','inspectorActionFormMode'].forEach(id => {
        const el = document.getElementById(id);
        el.classList.add('hidden'); el.classList.remove('flex');
    });
    document.getElementById('inspectorListMode').classList.remove('hidden');
    document.getElementById('inspectorMainTitle').innerHTML = '<span class="material-symbols-outlined text-red-500">warning</span> Deviations & Issues';
    const step = findStepById(currentSelectedStepIdStr);
    if (!step || !(step.deviations||[]).length) { setInspectorOpen(false); }
}

function openDeviationForm() {
    setInspectorOpen(true);
    ['inspectorImageMode','inspectorListMode','inspectorActionFormMode'].forEach(id => { const el=document.getElementById(id); el.classList.add('hidden'); el.classList.remove('flex'); });
    const hubCb = document.getElementById('createHubIncidentCheckbox');
    if(hubCb) { hubCb.checked=false; document.getElementById('hubIncidentTypeWrapper').classList.add('hidden'); }

    const ppeCb = document.getElementById('checkPartsCheckbox'); if(ppeCb) ppeCb.checked=false;
    const ssCb = document.getElementById('shopSupportCheckbox'); if(ssCb) ssCb.checked=false;
    document.getElementById('devReasonInput').value='';
    document.getElementById('devDetailsInput').value='';
    document.getElementById('inspectorFormMode').classList.remove('hidden'); document.getElementById('inspectorFormMode').classList.add('flex');
    document.getElementById('inspectorMainTitle').innerHTML = '<span class="material-symbols-outlined text-yellow-500">add_alert</span> Neue Deviation';
}

function saveNewDeviation() {
    const reason = document.getElementById('devReasonInput').value.trim();
    if (!reason) { alert('Bitte eine Kurzbeschreibung eingeben.'); return; }
    const details = document.getElementById('devDetailsInput').value.trim();
    const step = findStepById(currentSelectedStepIdStr);
    if (!step.deviations) step.deviations = [];
    const devNum = step.deviations.length + 1;
    const hubCb = document.getElementById('createHubIncidentCheckbox');
    const ppeCb = document.getElementById('checkPartsCheckbox');
    const ssCb = document.getElementById('shopSupportCheckbox');
    const hubType = hubCb && hubCb.checked ? document.getElementById('hubIncidentTypeSelect').value : '';
    const hubNr = '';

    if (hubCb && hubCb.checked && hubType) {
        const incidentUrls = {
            befund:      'https://mtuhub.service-now.com/esc?id=sc_cat_item&sys_id=205e46492b95ee900186f382ce91bfa7',
            schuettware: 'https://mtuhub.service-now.com/esc?id=sc_cat_item&sys_id=97e32caf1b06d2503f7b777c8b4bcb6d',
            bauteil:     'https://mtuhub.service-now.com/esc?id=sc_cat_item&sys_id=cc82b0e71b86d2503f7b777c8b4bcb34'
        };
        const baseUrl = incidentUrls[hubType];
        if (baseUrl && projectData && projectData.project_code) {
            const details = document.getElementById('devDetailsInput').value.trim();
            const hubUrl = `${baseUrl}&wbs=${projectData.project_code}&sdesc=${encodeURIComponent(reason)}&desc=${encodeURIComponent(details)}`;
            window.open(hubUrl, '_blank');
        }
    }

    step.deviations.push({
        id: `DEV-${String(devNum).padStart(3,'0')}`,
        status: 'Open',
        reason: reason,
        details: details,
        hub_incident_type: hubType,
        hub_incident_nr: hubNr,
        ppe_involved: ppeCb ? ppeCb.checked : false,
        shop_support: ssCb ? ssCb.checked : false,
        reported_by: LOGGED_IN_USER,
        reported_at: nowString(),
        thread: []
    });
    renderMainArea();
    saveProject();
}

function toggleDevPpeText(checkbox) {
    const ta = document.getElementById('devDetailsInput');
    if (!ta) return;
    const txt = "INFO: Bitte PPE involvieren und bestellte Teilnummern auf Richtigkeit prüfen lassen.\\n\\n";
    if (checkbox.checked) { if (!ta.value.startsWith(txt)) ta.value = txt + ta.value; }
    else { if (ta.value.startsWith(txt)) ta.value = ta.value.replace(txt, ""); }
}

function toggleDevShopSupportText(checkbox) {
    const ta = document.getElementById('devDetailsInput');
    if (!ta) return;
    const txt = "INFO: Shop Support ist notwendig\\n\\n";
    if (checkbox.checked) { if (!ta.value.startsWith(txt)) ta.value = txt + ta.value; }
    else { if (ta.value.startsWith(txt)) ta.value = ta.value.replace(txt, ""); }
}

function openActionForm(deviationId, actionType) {
    setInspectorOpen(true);
    ['inspectorImageMode','inspectorListMode','inspectorFormMode'].forEach(id => { const el=document.getElementById(id); el.classList.add('hidden'); el.classList.remove('flex'); });
    document.getElementById('inspectorActionFormMode').classList.remove('hidden'); document.getElementById('inspectorActionFormMode').classList.add('flex');
    document.getElementById('currentDeviationId').value = deviationId;
    document.getElementById('actionFormTextarea').value='';
    actionFormStagedFiles = [];
    document.getElementById('actionFormAttachmentPreview').innerHTML = '';
    document.getElementById('actionFormFileInput').value = '';

    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations||[]).find(d=>d.id===deviationId);
    const refWrapper = document.getElementById('actionFormRefWrapper');
    const refSelect = document.getElementById('actionFormRefSelect');

    if (actionType === 'comment') {
        refWrapper.style.display='none';
        document.getElementById('actionFormTitle').textContent=`Kommentar zu ${deviationId}`;
        document.getElementById('actionFormLabel').textContent='Kommentar';
        document.getElementById('actionFormSaveBtn').onclick=()=>saveAction(deviationId,'comment');
        document.getElementById('actionFormSaveBtn').className='px-3 py-1.5 bg-slate-600 text-white rounded text-sm font-bold shadow-sm hover:bg-slate-500';
        document.getElementById('actionFormSaveBtn').textContent='Kommentar speichern';
    } else if (actionType === 'troubleshooting') {
        refWrapper.style.display='none';
        document.getElementById('actionFormTitle').textContent=`Maßnahme zu ${deviationId}`;
        document.getElementById('actionFormLabel').textContent='Beschreibung der Maßnahme';
        document.getElementById('actionFormSaveBtn').onclick=()=>saveAction(deviationId,'troubleshooting');
        document.getElementById('actionFormSaveBtn').className='px-3 py-1.5 bg-orange-500 text-orange-950 rounded text-sm font-bold shadow-sm hover:bg-orange-400';
        document.getElementById('actionFormSaveBtn').textContent='Speichern';
    } else {
        refSelect.innerHTML='<option value="">Keine spezifische Maßnahme</option>';
        if(dev && dev.thread) dev.thread.filter(t=>t.type==='troubleshooting').forEach(t => { refSelect.innerHTML+=`<option value="${t.id}">${t.text.substring(0,40)}${t.text.length>40?'...':''}</option>`; });
        refWrapper.style.display='flex';
        document.getElementById('actionFormTitle').textContent=`Lösung für ${deviationId}`;
        document.getElementById('actionFormLabel').textContent='Beschreibung der Lösung';
        document.getElementById('actionFormSaveBtn').onclick=()=>saveAction(deviationId,'solution');
        document.getElementById('actionFormSaveBtn').className='px-3 py-1.5 bg-green-500 text-green-950 rounded text-sm font-bold shadow-sm hover:bg-green-400';
        document.getElementById('actionFormSaveBtn').textContent='Schließen & Lösung erfassen';
    }
    document.getElementById('inspectorMainTitle').innerHTML = `<span class="material-symbols-outlined text-orange-500">edit_note</span> ${deviationId}`;
}

async function saveAction(deviationId, actionType) {
    const text = document.getElementById('actionFormTextarea').value.trim();
    if (!text) { alert('Bitte eine Beschreibung eingeben.'); return; }
    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations||[]).find(d=>d.id===deviationId);
    if (!dev) return;
    if (!dev.thread) dev.thread=[];
    const entry = { id:`t-${Date.now()}`, type:actionType, text, author:LOGGED_IN_USER, date:nowString() };
    if (actionType==='solution') {
        const ref = document.getElementById('actionFormRefSelect').value;
        if(ref) entry.references=ref;
        dev.status='Closed';
    }
    if (actionFormStagedFiles.length > 0) {
        const uploaded = [];
        for (const staged of actionFormStagedFiles) {
            try {
                const fd = new FormData();
                fd.append('file', staged.file);
                const res = await fetch(`/api/deviation/attachments/${PROJECT_UUID}`, {method:'POST', body:fd});
                if (res.ok) { const data = await res.json(); uploaded.push({filename: data.filename}); }
            } catch(e) { console.error('Bild-Upload fehlgeschlagen', e); }
        }
        if (uploaded.length) entry.attachments = uploaded;
        actionFormStagedFiles = [];
    }
    dev.thread.push(entry);
    renderMainArea();
    saveProject();
    showInspectorList();
}

function stageActionFiles(input) {
    Array.from(input.files).forEach(file => {
        const id = `staged-${Date.now()}-${Math.random().toString(36).slice(2)}`;
        actionFormStagedFiles.push({id, file});
        const reader = new FileReader();
        reader.onload = e => {
            const preview = document.getElementById('actionFormAttachmentPreview');
            preview.insertAdjacentHTML('beforeend', `<div id="preview-${id}" class="relative w-16 h-16 rounded overflow-hidden border border-slate-200 shadow-sm group"><img src="${e.target.result}" class="w-full h-full object-cover"><button onclick="removeStagedFile('${id}')" class="absolute top-0 right-0 bg-red-500 text-white rounded-bl text-xs w-5 h-5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"><span class="material-symbols-outlined text-[12px]">close</span></button></div>`);
        };
        reader.readAsDataURL(file);
    });
    input.value = '';
}

function removeStagedFile(id) {
    actionFormStagedFiles = actionFormStagedFiles.filter(f => f.id !== id);
    const el = document.getElementById(`preview-${id}`);
    if (el) el.remove();
}

async function deleteDevAttachment(deviationId, threadId, filename) {
    if (!confirm('Bild wirklich löschen?')) return;
    try {
        const res = await fetch(`/api/deviation/attachments/${PROJECT_UUID}/${filename}`, {method:'DELETE'});
        if (!res.ok) { alert('Fehler beim Löschen.'); return; }
        const step = findStepById(currentSelectedStepIdStr);
        const dev = (step.deviations||[]).find(d=>d.id===deviationId);
        if (!dev) return;
        const entry = (dev.thread||[]).find(t=>t.id===threadId);
        if (!entry) return;
        entry.attachments = (entry.attachments||[]).filter(a=>a.filename!==filename);
        renderDeviations(step);
        saveProject();
    } catch(e) { alert('Netzwerkfehler beim Löschen.'); }
}

function _initImageZoomPan(containerEl, imgEl) {
    let scale = 1, panX = 0, panY = 0, isDragging = false, startX = 0, startY = 0;
    function apply() {
        imgEl.style.transform = `translate(${panX}px,${panY}px) scale(${scale})`;
        containerEl.style.cursor = scale > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default';
    }
    containerEl.addEventListener('wheel', e => {
        e.preventDefault();
        const factor = e.deltaY < 0 ? 1.18 : 0.85;
        scale = Math.min(Math.max(scale * factor, 1), 12);
        if (scale === 1) { panX = 0; panY = 0; }
        apply();
    }, { passive: false });
    containerEl.addEventListener('mousedown', e => {
        if (scale <= 1) return;
        isDragging = true; startX = e.clientX - panX; startY = e.clientY - panY; apply();
    });
    containerEl.addEventListener('mousemove', e => {
        if (!isDragging) return;
        panX = e.clientX - startX; panY = e.clientY - startY; apply();
    });
    containerEl.addEventListener('mouseup', () => { isDragging = false; apply(); });
    containerEl.addEventListener('mouseleave', () => { isDragging = false; });
    containerEl.addEventListener('dblclick', () => { scale = 1; panX = 0; panY = 0; apply(); });
}

function openDeviationImage(url) {
    setInspectorOpen(true);
    ['inspectorListMode','inspectorFormMode','inspectorActionFormMode'].forEach(id => { const el=document.getElementById(id); el.classList.add('hidden'); el.classList.remove('flex'); });
    const imgMode = document.getElementById('inspectorImageMode');
    imgMode.classList.remove('hidden'); imgMode.classList.add('flex');
    document.getElementById('inspectorMainTitle').innerHTML = '<span class="material-symbols-outlined text-blue-500">image</span> Bild';
    const content = document.getElementById('inspectorImageContent');
    content.innerHTML = `<img src="${url}" id="_zimg" class="max-w-full max-h-full object-contain rounded shadow-lg select-none" draggable="false" style="transform-origin:center center;">`;
    _initImageZoomPan(content, document.getElementById('_zimg'));
}

function reopenDeviation(deviationId) {
    if(confirm('Deviation wiedereröffnen?')) {
        const step = findStepById(currentSelectedStepIdStr);
        const dev = (step.deviations||[]).find(d=>d.id===deviationId);
        if(dev) { dev.status='Open'; renderMainArea(); saveProject(); }
    }
}

function saveHubNr(devId, value) {
    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations||[]).find(d=>d.id===devId);
    if (!dev) return;
    dev.hub_incident_nr = value === null ? '' : value.trim();
    const urlField = document.getElementById('hubUrlField_' + devId);
    if (urlField !== null) dev.hub_incident_url = urlField.value.trim();
    editHubNrId = null;
    renderDeviations(step);
    saveProject();
}

function editHubNr(devId) {
    editHubNrId = devId;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

function cancelEditHubNr() {
    editHubNrId = null;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

function toggleDeviationExpansion(devId) {
    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations||[]).find(d=>d.id===devId);
    if(dev) { dev.is_expanded = dev.is_expanded===false ? true : false; renderDeviations(step); }
}

function canManageDev(owner) { return IS_ADMIN || LOGGED_IN_USER === owner; }

function toggleKebab(id, event) {
    event.stopPropagation();
    openKebabId = openKebabId === id ? null : id;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

function startEditDeviation(devId, event) {
    event.stopPropagation();
    openKebabId = null;
    editingDeviationId = devId;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

function cancelEditDeviation() {
    editingDeviationId = null;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

function saveEditedDeviation(devId) {
    const reasonEl = document.getElementById(`editDevReason_${devId}`);
    const detailsEl = document.getElementById(`editDevDetails_${devId}`);
    if (!reasonEl || !reasonEl.value.trim()) { alert('Bitte einen Grund angeben.'); return; }
    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations || []).find(d => d.id === devId);
    if (!dev) return;
    dev.reason = reasonEl.value.trim();
    dev.details = detailsEl ? detailsEl.value.trim() : '';
    editingDeviationId = null;
    renderMainArea();
    saveProject();
}

function confirmDeleteDeviation(devId, event) {
    event.stopPropagation();
    openKebabId = null;
    if (!confirm('Deviation wirklich löschen? Alle Maßnahmen und Kommentare werden ebenfalls entfernt.')) return;
    const step = findStepById(currentSelectedStepIdStr);
    step.deviations = (step.deviations || []).filter(d => d.id !== devId);
    renderMainArea();
    saveProject();
}

function startEditThread(devId, threadId, event) {
    event.stopPropagation();
    openKebabId = null;
    actionFormStagedFiles = [];
    editingThreadKey = `${devId}::${threadId}`;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

function cancelEditThread() {
    actionFormStagedFiles = [];
    editingThreadKey = null;
    const step = findStepById(currentSelectedStepIdStr);
    if (step) renderDeviations(step);
}

async function saveEditedThread(devId, threadId) {
    const textEl = document.getElementById(`editThread_${threadId}`);
    if (!textEl || !textEl.value.trim()) { alert('Bitte einen Text eingeben.'); return; }
    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations || []).find(d => d.id === devId);
    if (!dev) return;
    const entry = (dev.thread || []).find(t => t.id === threadId);
    if (!entry) return;
    entry.text = textEl.value.trim();
    if (actionFormStagedFiles.length > 0) {
        const uploaded = [];
        for (const staged of actionFormStagedFiles) {
            try {
                const fd = new FormData();
                fd.append('file', staged.file);
                const res = await fetch(`/api/deviation/attachments/${PROJECT_UUID}`, {method:'POST', body:fd});
                if (res.ok) { const data = await res.json(); uploaded.push({filename: data.filename}); }
            } catch(e) { console.error('Bild-Upload fehlgeschlagen', e); }
        }
        if (uploaded.length) entry.attachments = [...(entry.attachments || []), ...uploaded];
        actionFormStagedFiles = [];
    }
    editingThreadKey = null;
    renderDeviations(step);
    saveProject();
}

function stageThreadEditFiles(input, threadId) {
    Array.from(input.files).forEach(file => {
        const id = `staged-${Date.now()}-${Math.random().toString(36).slice(2)}`;
        actionFormStagedFiles.push({id, file});
        const reader = new FileReader();
        reader.onload = e => {
            const preview = document.getElementById(`editThreadPreview_${threadId}`);
            if (preview) preview.insertAdjacentHTML('beforeend', `<div id="preview-${id}" class="relative w-16 h-16 rounded overflow-hidden border border-slate-200 shadow-sm group"><img src="${e.target.result}" class="w-full h-full object-cover"><button onclick="event.stopPropagation();removeStagedFile('${id}')" class="absolute top-0 right-0 bg-red-500 text-white rounded-bl text-xs w-5 h-5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"><span class="material-symbols-outlined text-[12px]">close</span></button></div>`);
        };
        reader.readAsDataURL(file);
    });
    input.value = '';
}

function confirmDeleteThread(devId, threadId, event) {
    event.stopPropagation();
    openKebabId = null;
    if (!confirm('Eintrag wirklich löschen?')) return;
    const step = findStepById(currentSelectedStepIdStr);
    const dev = (step.deviations || []).find(d => d.id === devId);
    if (!dev) return;
    dev.thread = (dev.thread || []).filter(t => t.id !== threadId);
    renderDeviations(step);
    saveProject();
}

function renderDeviations(step) {
    const container = document.getElementById('deviationsContainer');
    const devs = step.deviations || [];
    if (!devs.length) {
        container.innerHTML = `<div class="text-center py-10 bg-white border border-slate-200 rounded-lg shadow-sm"><span class="material-symbols-outlined text-slate-300 text-4xl mb-2">task_alt</span><p class="text-slate-500 text-sm">Keine Abweichungen gemeldet.</p></div>`; return;
    }
    const sortedDevs = [...devs].sort((a, b) => {
        if (a.status === 'Closed' && b.status !== 'Closed') return 1;
        if (a.status !== 'Closed' && b.status === 'Closed') return -1;
        return 0;
    });
    container.innerHTML = sortedDevs.map(dev => {
        // Edit mode for this deviation
        if (editingDeviationId === dev.id) {
            return `<div class="bg-white border border-blue-300 rounded-lg shadow-sm overflow-hidden">
                <div class="p-3" onclick="event.stopPropagation()">
                    <div class="flex justify-between items-center mb-2">
                        <span class="text-xs font-mono font-semibold text-slate-500">${dev.id} — Bearbeiten</span>
                        <button onclick="cancelEditDeviation()" class="text-slate-400 hover:text-slate-600 p-0.5 rounded"><span class="material-symbols-outlined text-[18px]">close</span></button>
                    </div>
                    <label class="flex flex-col gap-1 mb-2">
                        <span class="text-xs font-medium text-slate-600">Kurzbeschreibung</span>
                        <input id="editDevReason_${dev.id}" type="text" value="${(dev.reason||'').replace(/"/g,'&quot;')}" class="text-sm border border-slate-300 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-400">
                    </label>
                    <label class="flex flex-col gap-1 mb-3">
                        <span class="text-xs font-medium text-slate-600">Details</span>
                        <textarea id="editDevDetails_${dev.id}" rows="3" class="text-sm border border-slate-300 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-400">${dev.details||''}</textarea>
                    </label>
                    <div class="flex gap-2">
                        <button onclick="saveEditedDeviation('${dev.id}')" class="flex-1 py-1.5 bg-blue-600 text-white rounded text-xs font-semibold hover:bg-blue-700">Speichern</button>
                        <button onclick="cancelEditDeviation()" class="flex-1 py-1.5 bg-slate-100 text-slate-600 rounded text-xs font-semibold hover:bg-slate-200">Abbrechen</button>
                    </div>
                </div>
            </div>`;
        }

        const isExpanded = dev.status === 'Closed' ? (dev.is_expanded === true) : (dev.is_expanded !== false);
        const timelineItems = [];
        const hubTypeLabels = {befund:'Befundbeauftragung', schuettware:'Schüttware', bauteil:'Bauteil'};
        // Backward-compat: old data stored hub_incident as combined string
        const hubType = dev.hub_incident_type || (dev.hub_incident ? 'befund' : '');
        const hubNr = dev.hub_incident_nr !== undefined ? dev.hub_incident_nr : (dev.hub_incident || '');
        const hubUrl = dev.hub_incident_url || '';
        const hubTypeLabel = hubTypeLabels[hubType] || hubType;
        if(hubType) {
            const hubNrContent = (hubNr && editHubNrId !== dev.id)
                ? `<div class="flex items-center justify-between"><div class="flex items-center gap-2"><span class="material-symbols-outlined text-blue-600" style="font-size:18px;">confirmation_number</span><div><span class="text-[12px] text-blue-800">${hubTypeLabel} — Inc. Nr.: <strong>${hubNr}</strong></span>${hubUrl ? '<a href="' + hubUrl.replace(/"/g,'&quot;') + '" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()" class="ml-2 inline-flex items-center text-blue-500 hover:text-blue-700" title="' + hubUrl.replace(/"/g,'&quot;') + '"><span class="material-symbols-outlined" style="font-size:13px;vertical-align:middle;">open_in_new</span></a>' : ''}</div></div><button onclick="event.stopPropagation();editHubNr('${dev.id}')" class="text-[11px] text-blue-600 hover:text-blue-900 underline">Ändern</button></div>`
                : `<div class="flex items-center gap-2 mb-1.5"><span class="material-symbols-outlined text-blue-600" style="font-size:18px;">open_in_new</span><span class="text-[12px] text-blue-800">${hubTypeLabel} — Incident eröffnet</span></div><div class="flex flex-col gap-1.5"><div class="flex items-center gap-2"><span class="text-[11px] font-semibold text-blue-700 w-14 shrink-0">Inc. Nr.:</span><input id="hubNrField_${dev.id}" type="text" value="${hubNr.replace(/"/g,'&quot;')}" placeholder="z.B. INC-10293" class="text-xs font-mono border border-blue-300 bg-white rounded px-2 py-1 text-blue-900 w-36 focus:outline-none focus:ring-1 focus:ring-blue-400" onclick="event.stopPropagation()" onkeydown="event.stopPropagation();"></div><div class="flex items-center gap-2"><span class="text-[11px] font-semibold text-blue-700 w-14 shrink-0">URL:</span><input id="hubUrlField_${dev.id}" type="text" value="${hubUrl.replace(/"/g,'&quot;')}" placeholder="https://..." class="text-xs border border-blue-300 bg-white rounded px-2 py-1 text-blue-900 flex-1 focus:outline-none focus:ring-1 focus:ring-blue-400" onclick="event.stopPropagation()" onkeydown="event.stopPropagation();if(event.key==='Enter'){event.preventDefault();saveHubNr('${dev.id}',document.getElementById('hubNrField_${dev.id}').value);}"></div><div class="flex items-center gap-2 mt-0.5"><button onclick="event.stopPropagation();saveHubNr('${dev.id}',document.getElementById('hubNrField_${dev.id}').value)" class="text-[11px] font-semibold px-2 py-1 bg-blue-600 text-white rounded hover:bg-blue-700">Speichern</button>${hubNr ? '<button onclick="event.stopPropagation();cancelEditHubNr()" class="text-[11px] text-slate-500 hover:text-slate-700 underline ml-1">Abbrechen</button>' : ''}</div></div>`;
            timelineItems.push(`<div class="relative pl-5 pb-4"><div class="absolute -left-[5px] top-1 w-2 h-2 rounded-full bg-blue-500 ring-4 ring-white z-10"></div><div class="bg-blue-50 border border-blue-200 rounded-lg p-2.5">${hubNrContent}</div></div>`);
        }
        (dev.thread||[]).forEach(t => {
            const dotColor = t.type==='solution' ? 'bg-green-500' : t.type==='comment' ? 'bg-slate-400' : 'bg-orange-400';
            const typeColor = t.type==='solution' ? 'text-green-600' : t.type==='comment' ? 'text-slate-500' : 'text-orange-600';
            const typeLabel = t.type==='solution' ? 'Lösung' : t.type==='comment' ? 'Kommentar' : 'Maßnahme';
            let refHtml='';
            if(t.type==='solution' && t.references) { const ref=(dev.thread||[]).find(x=>x.id===t.references); if(ref) refHtml=`<div class="mt-2 p-2 bg-green-100 border border-green-200 rounded text-xs text-green-800 flex items-start gap-1"><span class="material-symbols-outlined text-[14px] mt-0.5">verified</span><div><span class="font-bold block">Erfolgreiche Maßnahme:</span>${ref.text}</div></div>`; }
            let attachHtml='';
            if(t.attachments && t.attachments.length) { attachHtml=`<div class="mt-2 flex flex-wrap gap-1.5">${t.attachments.map(a=>`<div class="relative group w-16 h-16 rounded overflow-hidden border border-slate-200 cursor-pointer shadow-sm" onclick="openDeviationImage('/api/deviation/attachments/${PROJECT_UUID}/${a.filename}')"><img src="/api/deviation/attachments/${PROJECT_UUID}/${a.filename}" class="w-full h-full object-cover"><button onclick="event.stopPropagation();deleteDevAttachment('${dev.id}','${t.id}','${a.filename}')" class="absolute top-0 right-0 bg-red-500 text-white opacity-0 group-hover:opacity-100 transition-opacity rounded-bl w-5 h-5 flex items-center justify-center"><span class="material-symbols-outlined text-[12px]">delete</span></button></div>`).join('')}</div>`; }

            const threadKebabId = `${dev.id}::${t.id}`;
            const isEditingThread = editingThreadKey === threadKebabId;
            const threadKebabOpen = openKebabId === threadKebabId;
            const canManageThread = canManageDev(t.author);

            let threadEntryHtml;
            if (isEditingThread) {
                const existingImgsHtml = (t.attachments && t.attachments.length)
                    ? `<div class="flex flex-wrap gap-1.5 mb-2">${t.attachments.map(a=>`<div class="relative group w-16 h-16 rounded overflow-hidden border border-slate-200 shadow-sm"><img src="/api/deviation/attachments/${PROJECT_UUID}/${a.filename}" class="w-full h-full object-cover"><button onclick="event.stopPropagation();deleteDevAttachment('${dev.id}','${t.id}','${a.filename}')" class="absolute top-0 right-0 bg-red-500 text-white opacity-0 group-hover:opacity-100 transition-opacity rounded-bl w-5 h-5 flex items-center justify-center"><span class="material-symbols-outlined text-[12px]">delete</span></button></div>`).join('')}</div>`
                    : '';
                threadEntryHtml = `<div class="bg-white border border-blue-300 rounded-lg p-2.5 shadow-sm" onclick="event.stopPropagation()">
                    <div class="mb-1.5 flex justify-between items-center">
                        <span class="text-[10px] font-bold uppercase ${typeColor}">${typeLabel} — Bearbeiten</span>
                        <button onclick="cancelEditThread()" class="text-slate-400 hover:text-slate-600"><span class="material-symbols-outlined text-[16px]">close</span></button>
                    </div>
                    <textarea id="editThread_${t.id}" rows="3" class="w-full text-[13px] border border-slate-300 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-400">${t.text}</textarea>
                    <div class="mt-2">
                        ${existingImgsHtml}
                        <div class="flex items-center gap-2 mb-1.5">
                            <button type="button" onclick="document.getElementById('editThreadFileInput_${t.id}').click()" class="text-xs text-slate-500 hover:text-slate-700 flex items-center gap-1 border border-slate-300 rounded px-2 py-0.5 bg-white hover:bg-slate-50"><span class="material-symbols-outlined text-sm">add_photo_alternate</span> Bild hinzufügen</button>
                            <input type="file" id="editThreadFileInput_${t.id}" accept="image/*" multiple class="hidden" onchange="stageThreadEditFiles(this,'${t.id}')">
                        </div>
                        <div id="editThreadPreview_${t.id}" class="flex flex-wrap gap-1.5"></div>
                    </div>
                    <div class="flex gap-2 mt-2">
                        <button onclick="saveEditedThread('${dev.id}','${t.id}')" class="flex-1 py-1 bg-blue-600 text-white rounded text-xs font-semibold hover:bg-blue-700">Speichern</button>
                        <button onclick="cancelEditThread()" class="flex-1 py-1 bg-slate-100 text-slate-600 rounded text-xs font-semibold hover:bg-slate-200">Abbrechen</button>
                    </div>
                </div>`;
            } else {
                threadEntryHtml = `<div class="bg-white border border-slate-200 rounded-lg p-2.5 shadow-sm">
                    <div class="flex justify-between items-start mb-1">
                        <div><span class="text-[10px] font-bold uppercase ${typeColor}">${typeLabel}</span><p class="text-[9px] text-slate-400 font-mono mt-0.5">${t.author||''} &middot; ${t.date||''}</p></div>
                        ${canManageThread ? `<div class="relative ml-1 flex-shrink-0" onclick="event.stopPropagation()">
                            <button onclick="toggleKebab('${threadKebabId}',event)" class="p-0.5 rounded hover:bg-slate-100 text-slate-300 hover:text-slate-500"><span class="material-symbols-outlined" style="font-size:16px;">more_vert</span></button>
                            ${threadKebabOpen ? `<div class="absolute right-0 top-6 bg-white border border-slate-200 rounded-lg shadow-lg z-50 min-w-[8rem] py-1">
                                <button onclick="startEditThread('${dev.id}','${t.id}',event)" class="w-full text-left px-3 py-1.5 text-xs text-slate-700 hover:bg-slate-50 flex items-center gap-2"><span class="material-symbols-outlined text-[14px]">edit</span> Bearbeiten</button>
                                <button onclick="confirmDeleteThread('${dev.id}','${t.id}',event)" class="w-full text-left px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 flex items-center gap-2"><span class="material-symbols-outlined text-[14px]">delete</span> Löschen</button>
                            </div>` : ''}
                        </div>` : ''}
                    </div>
                    <p class="text-[13px] text-slate-700 leading-snug">${t.text}</p>${refHtml}${attachHtml}
                </div>`;
            }
            timelineItems.push(`<div class="relative pl-5 pb-4"><div class="absolute -left-[5px] top-1 w-2 h-2 rounded-full ${dotColor} ring-4 ring-white z-10"></div>${threadEntryHtml}</div>`);
        });
        const threadHtml = timelineItems.map((item, i) => `<div class="${i<timelineItems.length-1?'border-l-2 border-slate-200':''}">${item}</div>`).join('');
        const hClass = dev.status==='Closed' ? 'bg-green-50/50 border-green-100 hover:bg-green-100/50' : 'bg-red-50/50 border-red-100 hover:bg-red-100/50';
        const devKebabOpen = openKebabId === dev.id;
        const canManage = canManageDev(dev.reported_by);

        return `<div class="bg-white border rounded-lg shadow-sm overflow-hidden">
            <div class="p-3 cursor-pointer transition-colors ${hClass}${isExpanded?' border-b':''}" onclick="toggleDeviationExpansion('${dev.id}')">
                <div class="flex justify-between items-start">
                    <div class="flex-1 pr-2">
                        <div class="flex items-center gap-2 mb-1">
                            <span class="${dev.status==='Closed'?'bg-green-600':'bg-red-600'} text-white text-[9px] font-bold px-1.5 py-0.5 rounded uppercase">${dev.status}</span>
                            <span class="text-[10px] font-mono text-slate-500">${dev.id}</span>
                        </div>
                        <h3 class="text-sm font-bold leading-snug ${dev.status==='Closed'?'text-green-900':'text-red-900'}">${dev.reason}</h3>
                        ${dev.details ? `<div class="mt-1"><p id="devdet-${dev.id}" class="text-[11px] text-slate-600 leading-snug" style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">${dev.details}</p><button onclick="event.stopPropagation();toggleDevDetails('${dev.id}')" id="devdet-btn-${dev.id}" class="text-[10px] text-blue-500 hover:text-blue-700 font-medium mt-0.5">... mehr</button></div>` : ''}
                        <p class="text-[11px] mt-1 ${dev.status==='Closed'?'text-green-700':'text-red-700'}">${dev.reported_by||''} ${dev.reported_at?'('+dev.reported_at+')':''}</p>
                        ${dev.ppe_involved||dev.shop_support ? `<div class="flex gap-1 mt-1">${dev.ppe_involved?'<span class="text-[9px] font-bold bg-purple-100 text-purple-700 px-1.5 py-0.5 rounded uppercase">PPE</span>':''}${dev.shop_support?'<span class="text-[9px] font-bold bg-cyan-100 text-cyan-700 px-1.5 py-0.5 rounded uppercase">Shop Support</span>':''}</div>` : ''}
                    </div>
                    <div class="flex items-center gap-1 flex-shrink-0">
                        ${canManage ? `<div class="relative" onclick="event.stopPropagation()">
                            <button onclick="toggleKebab('${dev.id}',event)" class="p-1 rounded hover:bg-slate-200/60 text-slate-400 hover:text-slate-600" title="Optionen"><span class="material-symbols-outlined" style="font-size:18px;">more_vert</span></button>
                            ${devKebabOpen ? `<div class="absolute right-0 top-8 bg-white border border-slate-200 rounded-lg shadow-lg z-50 min-w-[8.5rem] py-1">
                                <button onclick="startEditDeviation('${dev.id}',event)" class="w-full text-left px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-50 flex items-center gap-2"><span class="material-symbols-outlined text-[16px]">edit</span> Bearbeiten</button>
                                <button onclick="confirmDeleteDeviation('${dev.id}',event)" class="w-full text-left px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 flex items-center gap-2"><span class="material-symbols-outlined text-[16px]">delete</span> Löschen</button>
                            </div>` : ''}
                        </div>` : ''}
                        <span class="material-symbols-outlined expand-toggle-btn text-slate-400 ${isExpanded?'rotate-0':'-rotate-90'}" style="font-size:20px;">expand_more</span>
                    </div>
                </div>
            </div>
            ${isExpanded ? `<div class="p-4">
                ${threadHtml ? `<div class="ml-1 mt-1">${threadHtml}</div>` : '<p class="text-xs text-slate-500 italic mb-3">Noch keine Maßnahmen dokumentiert.</p>'}
                ${dev.status!=='Closed' ? `<div class="mt-2 pt-2 border-t border-slate-100 flex flex-col gap-2">
                    <div class="flex gap-2">
                        <button onclick="event.stopPropagation();openActionForm('${dev.id}','troubleshooting')" class="flex-1 py-1.5 bg-orange-50 border border-orange-200 text-orange-700 rounded text-xs font-semibold hover:bg-orange-100">Neue Maßnahme</button>
                        <button onclick="event.stopPropagation();openActionForm('${dev.id}','comment')" class="flex-1 py-1.5 bg-slate-50 border border-slate-200 text-slate-600 rounded text-xs font-semibold hover:bg-slate-100">Kommentar</button>
                    </div>
                    <button onclick="event.stopPropagation();openActionForm('${dev.id}','solution')" class="w-full py-1.5 bg-green-50 border border-green-200 text-green-700 rounded text-xs font-semibold hover:bg-green-100">Lösung & Schließen</button>
                </div>` : `<div class="mt-2 pt-2 border-t border-slate-200">
                    <button onclick="event.stopPropagation();reopenDeviation('${dev.id}')" class="w-full py-1.5 bg-white border border-slate-300 text-slate-600 rounded text-xs font-semibold hover:bg-slate-50 flex items-center justify-center gap-1 shadow-sm"><span class="material-symbols-outlined text-[16px]">undo</span> Wiedereröffnen</button>
                </div>`}
            </div>` : ''}
        </div>`;
    }).join('');
}

function openImagePanel(imageRef, displayLabel, event) {
    if(event) { event.preventDefault(); event.stopPropagation(); }
    const filename = imageRef.split('|')[0].trim();
    const label = displayLabel || filename;
    setInspectorOpen(true);
    ['inspectorListMode','inspectorFormMode','inspectorActionFormMode'].forEach(id => { const el=document.getElementById(id); el.classList.add('hidden'); el.classList.remove('flex'); });
    document.getElementById('inspectorImageMode').classList.remove('hidden'); document.getElementById('inspectorImageMode').classList.add('flex');
    document.getElementById('inspectorMainTitle').innerHTML = `<div class="flex flex-col gap-0.5"><span class="font-mono font-bold text-blue-700 text-sm leading-tight">${label}</span><span class="text-[10px] text-slate-400 font-normal">Referenzbild aus der Arbeitsanweisung.</span></div>`;
    const imgUrl = `/api/template_images/${encodeURIComponent(filename)}`;
    const content = document.getElementById('inspectorImageContent');
    content.innerHTML = `<img src="${imgUrl}" id="_zimg" alt="${label}" class="max-w-full max-h-full object-contain rounded shadow-lg select-none" draggable="false" style="transform-origin:center center;" onerror="this.replaceWith(Object.assign(document.createElement('p'),{className:'font-mono text-sm text-red-400 bg-slate-800 border border-red-700 px-4 py-2 rounded-lg text-center',textContent:'Bild nicht gefunden: ${filename}'}))">`;
    _initImageZoomPan(content, document.getElementById('_zimg'));
}

// --- Comments ---
function toggleComments() {
    commentsOpen = !commentsOpen;
    const panel = document.getElementById('commentsPanel');
    if (commentsOpen) {
        if (laufkartenPanelOpen) { laufkartenPanelOpen = false; document.getElementById('laufkartenPanel').classList.remove('open'); }
        panel.classList.add('open'); renderComments();
    } else { panel.classList.remove('open'); }
}

async function loadComments() {
    try {
        const projectId = `${projectData.project_code}_${projectData.engine_serial}`;
        const res = await fetch(`${COMMENTS_API_BASE}${encodeURIComponent(projectId)}`);
        if (!res.ok) return;
        cachedComments = await res.json();
        const label = document.getElementById('commentsBtnLabel');
        label.textContent = cachedComments.length ? `Kommentare (${cachedComments.length})` : 'Kommentare';
    } catch(e) { /* ignore */ }
}

function renderComments() {
    const el = document.getElementById('commentsContent');
    if (!cachedComments.length) { el.innerHTML='<p class="text-xs text-slate-400 italic">Noch keine Kommentare.</p>'; return; }
    el.innerHTML = cachedComments.map(c =>
        `<div class="bg-white border border-slate-200 rounded-md p-2 shadow-sm"><div class="flex justify-between items-center mb-1"><span class="text-xs font-semibold text-slate-700">${c.author||'System'}</span><span class="text-[10px] text-slate-400">${c.timestamp ? new Date(c.timestamp).toLocaleString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) : ''}</span></div><p class="text-xs text-slate-600">${c.comment||''}</p></div>`
    ).join('');
}

async function addComment() {
    const input = document.getElementById('commentInput');
    const text = input.value.trim();
    if (!text) return;
    try {
        const projectId = `${projectData.project_code}_${projectData.engine_serial}`;
        await fetch(`/api/comments/${encodeURIComponent(projectId)}`, {
            method: 'POST', headers: {'Content-Type':'application/json'},
            body: JSON.stringify({ comment: text })
        });
        input.value='';
        await loadComments();
        renderComments();
    } catch(e) { alert('Fehler beim Speichern.'); }
}

// --- Save to server ---
async function saveProject() {
    if (isSaving || !projectData) return;
    isSaving = true;
    try {
        const projectId = `${projectData.project_code}_${projectData.engine_serial}`;
        await fetch(`${SAVE_API_URL}${encodeURIComponent(projectId)}`, {
            method: 'POST', headers: {'Content-Type':'application/json'},
            body: JSON.stringify(projectData)
        });
    } catch(e) { console.error('Fehler beim Speichern:', e); }
    finally { isSaving = false; }
}

// --- Init ---
loadProject();
</script>
</body>
</html>
"""

ENGINEER_REPORT_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MTU – Engineer Report</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Mono:wght@400;600&display=swap" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms"></script>
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <style>
        body { font-family: 'Inter', sans-serif; color: #1e293b; }
        .font-mono { font-family: 'Noto Sans Mono', monospace; }
        .material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; }

        /* Sidebar */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label,
        #sidebar.is-collapsed .sidebar-title,
        #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link,
        #sidebar.is-collapsed .sidebar-header,
        #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined,
        #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }

        /* Left step column */
        #stepCol { width: var(--tree-width, 460px); min-width: 200px; max-width: 600px; position: relative; flex-shrink: 0; }
        #stepCol.is-resizing { transition: none; }
        .step-col-resizer {
            position: absolute; right: -3px; top: 0; bottom: 0; width: 6px;
            cursor: col-resize; z-index: 20; display: flex; align-items: center; justify-content: center;
        }
        .step-col-resizer:hover, .step-col-resizer.is-resizing { background-color: #3b82f6; }
        .step-col-resizer::after { content:""; height:24px; width:2px; background:#cbd5e1; border-radius:1px; transition:background 0.2s; }
        .step-col-resizer:hover::after, .step-col-resizer.is-resizing::after { background: white; }

        /* Step items */
        .step-btn {
            display: flex; align-items: center; gap: 0.5rem;
            width: 100%; text-align: left;
            padding: 0.4rem 0.6rem;
            border-radius: 0.4rem;
            border: 1px solid transparent;
            cursor: pointer; background: none;
            transition: background 0.1s, border-color 0.1s;
        }
        .step-btn:hover { background: #f8fafc; border-color: #e2e8f0; }
        .step-btn.selected { background: #eff6ff; border-color: #bfdbfe; box-shadow: inset 3px 0 0 #2563eb; }
        .step-dot { width: 10px; height: 10px; border-radius: 9999px; flex-shrink: 0; }

        /* Badges in step tree */
        .node-badge {
            display: inline-flex; align-items: center;
            padding: 0.05rem 0.35rem; border-radius: 9999px;
            font-size: 0.65rem; font-weight: 700; flex-shrink: 0;
        }

        /* Content area cards */
        .report-card { background: white; border-radius: 0.75rem; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
        .report-card-placeholder {
            background: white; border-radius: 0.75rem; border: 2px dashed #e2e8f0;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            gap: 0.5rem; min-height: 140px; color: #94a3b8;
        }
        .stat-card { background: white; border-radius: 0.75rem; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.04); padding: 1.25rem 1.5rem; }

        /* API node cards */
        .api-node {
            background: white; border-radius: 0.75rem; border: 2px dashed #e2e8f0;
            padding: 1.25rem; display: flex; flex-direction: column; align-items: center;
            justify-content: center; gap: 0.5rem; min-height: 120px; transition: border-color 0.15s;
        }
        .api-node:hover { border-color: #bfdbfe; }

        .data-table { width: 100%; border-collapse: collapse; }
        .data-table th {
            text-align: left; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.06em;
            text-transform: uppercase; color: #64748b; padding: 0.625rem 1rem;
            border-bottom: 1px solid #f1f5f9; background: #f8fafc;
        }
        .data-table td { padding: 0.75rem 1rem; font-size: 0.8125rem; color: #334155; border-bottom: 1px solid #f8fafc; vertical-align: top; }
        .data-table tr:last-child td { border-bottom: none; }
        .data-table tr:hover td { background: #f8fafc; }

        .badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.2rem 0.6rem; border-radius: 9999px; font-size: 0.7rem; font-weight: 600; }
        .badge-green  { background: #dcfce7; color: #166534; }
        .badge-red    { background: #fee2e2; color: #991b1b; }
        .badge-yellow { background: #fef9c3; color: #854d0e; }
        .badge-blue   { background: #dbeafe; color: #1e40af; }
        .badge-gray   { background: #f1f5f9; color: #475569; }

        .cycle-tab {
            padding: 0.2rem 0.65rem; border-radius: 9999px; font-size: 0.7rem; font-weight: 600;
            color: #64748b; border: 1px solid #e2e8f0; background: white;
            cursor: pointer; transition: all 0.15s;
        }
        .cycle-tab:hover { background: #f1f5f9; }
        .cycle-tab.active-tab { background: #1e40af; color: white; border-color: #1e40af; }
        .cycle-group-header { flex-shrink: 0; }

        @media print {
            #sidebar, #stepCol, .no-print { display: none !important; }
            body { overflow: auto !important; height: auto !important; }
            #contentArea { overflow: visible !important; }
        }
    </style>
</head>
<body class="bg-white h-screen flex overflow-hidden">

    {% include 'sidebar.html' %}

    <div class="flex-1 flex flex-col overflow-hidden">

        <!-- 3-column area -->
        <div class="flex flex-1 overflow-hidden">

            <!-- ═══ LEFT COLUMN: Project info + Step tree ═══ -->
            <div id="stepCol" class="bg-white border-r border-slate-200 flex flex-col overflow-hidden">
                <div class="step-col-resizer" id="stepColResizer"></div>

                <!-- Zurück-Link -->
                <div class="px-4 pt-3 pb-0 flex-shrink-0">
                    <a href="{{ back_url }}" class="inline-flex items-center gap-1 text-xs text-slate-400 hover:text-blue-600 transition-colors">
                        <span class="material-symbols-outlined text-[15px]">arrow_back</span>
                        Zurück zur Timeline
                    </a>
                </div>

                <!-- Project header -->
                <div class="px-5 py-5 border-b border-slate-200 bg-white flex-shrink-0">
                    <h1 class="text-xl font-bold text-slate-900 tracking-tight leading-snug">{{ project.get('name', project.get('project_code','—')) }}</h1>
                    <div class="text-[11px] text-slate-400 font-mono mt-1 mb-4">
                        SN: {{ project.get('engine_serial', 'N/A') }} | {{ project.get('engine_type', 'N/A') }}
                    </div>
                    <div class="flex items-center gap-2.5">
                        <span class="text-slate-400 text-[10px] font-semibold uppercase tracking-wide shrink-0">Fortschritt</span>
                        <div class="flex-1 rounded-full bg-slate-100 h-1.5 overflow-hidden border border-slate-200">
                            <div class="h-full rounded-full bg-blue-500 transition-all duration-500" style="width:{{ project.get('progress_percent', 0)|int }}%"></div>
                        </div>
                        <span class="text-blue-600 text-[11px] font-bold shrink-0">{{ project.get('progress_percent', 0)|int }}%</span>
                    </div>
                    <div class="mt-4 flex items-center gap-3">
                        <button onclick="window.print()" class="no-print flex items-center gap-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors">
                            <span class="material-symbols-outlined text-[14px]">print</span>
                            Drucken
                        </button>
                        <a href="{{ url_for('serve_project_protocol', project_uuid=project_uuid) }}" target="_blank"
                           class="no-print flex items-center gap-1.5 text-xs text-slate-400 hover:text-blue-600 transition-colors">
                            <span class="material-symbols-outlined text-[14px]">assignment</span>
                            Protocol
                        </a>
                        <a href="{{ url_for('serve_project_detail_page', project_uuid=project_uuid) }}"
                           class="flex items-center gap-1.5 text-xs text-slate-400 hover:text-blue-600 transition-colors">
                            <span class="material-symbols-outlined text-[14px]">open_in_new</span>
                            Zum Projekt
                        </a>
                    </div>
                </div>

                <!-- Overview button -->
                <div class="px-3 pt-3 pb-1 flex-shrink-0">
                    <button id="overviewBtn" class="step-btn selected" onclick="selectOverview()">
                        <span class="material-icons-outlined text-slate-400" style="font-size:15px;">dashboard</span>
                        <span class="text-xs font-semibold text-slate-700 flex-1">Übersicht</span>
                        <span class="node-badge bg-slate-100 text-slate-500">{{ (parts | rejectattr('nr') | list | length) + deviations|length }}</span>
                    </button>
                </div>

                <p class="px-4 pt-3 pb-1 text-[10px] font-bold uppercase tracking-wider text-slate-400 flex-shrink-0">Schritte</p>

                <!-- Step list -->
                <div class="flex-1 overflow-y-auto px-3 pb-4 space-y-0.5">
                {% if max_cycle > 1 %}{% set ns = namespace(cur_cycle=0) %}{% endif %}
                {% for step in report_steps %}
                    {% set cn = step.cycle_number %}
                    {% if max_cycle > 1 and cn != ns.cur_cycle %}
                    {% set ns.cur_cycle = cn %}
                    <div class="cycle-group-header pt-2 pb-0.5 px-1 flex items-center gap-2" data-cycle="{{ cn }}">
                        <span class="text-[10px] font-bold uppercase tracking-wider text-slate-400">Zyklus {{ cn }}</span>
                        <div class="flex-1 h-px bg-slate-100"></div>
                    </div>
                    {% endif %}
                    {% set dot = 'bg-green-500' if step.status == 'Completed' else ('bg-blue-500' if step.status == 'In Progress' else ('bg-amber-400' if step.status == 'On Hold' else ('bg-slate-200' if step.status == 'N/R' else 'bg-slate-300'))) %}
                    <button class="step-btn{% if step.status == 'N/R' %} opacity-50{% endif %}" data-step-id="{{ step.id_str }}" data-cycle="{{ cn }}"
                            onclick="selectStep('{{ step.id_str }}', '{{ step.name | replace("'", "\\'") }}', '{{ step.status }}')">
                        <span class="step-dot {{ dot }}"></span>
                        <span class="text-xs {% if step.status == 'N/R' %}text-slate-400 line-through{% else %}text-slate-700 font-medium{% endif %} flex-1 truncate">{{ step.name }}</span>
                        {% if step.status == 'N/R' %}
                        <span class="text-[10px] font-mono text-slate-400 flex-shrink-0">N/R</span>
                        {% else %}
                        {% if step.parts_count > 0 %}
                        <span class="node-badge bg-blue-50 text-blue-700">{{ step.parts_count }}T</span>
                        {% endif %}
                        {% if step.deviations_count > 0 %}
                        <span class="node-badge {{ 'bg-red-100 text-red-700' if step.open_deviations_count > 0 else 'bg-green-100 text-green-700' }}">{{ step.deviations_count }}D</span>
                        {% endif %}
                        {% endif %}
                    </button>
                    {% for child in step.children %}
                    {% set cdot = 'bg-green-500' if child.status == 'Completed' else ('bg-blue-500' if child.status == 'In Progress' else ('bg-amber-400' if child.status == 'On Hold' else ('bg-slate-200' if child.status == 'N/R' else 'bg-slate-300'))) %}
                    <button class="step-btn pl-5{% if child.status == 'N/R' %} opacity-50{% endif %}" data-step-id="{{ child.id_str }}" data-cycle="{{ child.cycle_number }}"
                            onclick="selectStep('{{ child.id_str }}', '{{ child.name | replace("'", "\\'") }}', '{{ child.status }}')">
                        <span class="w-2 flex-shrink-0 border-l border-b border-slate-200 h-3 rounded-bl ml-1"></span>
                        <span class="step-dot {{ cdot }}" style="width:8px;height:8px;"></span>
                        <span class="text-xs {% if child.status == 'N/R' %}text-slate-400 line-through{% else %}text-slate-600{% endif %} flex-1 truncate">{{ child.name }}</span>
                        {% if child.status == 'N/R' %}
                        <span class="text-[10px] font-mono text-slate-400 flex-shrink-0">N/R</span>
                        {% else %}
                        {% if child.parts_count > 0 %}
                        <span class="node-badge bg-blue-50 text-blue-700">{{ child.parts_count }}T</span>
                        {% endif %}
                        {% if child.deviations_count > 0 %}
                        <span class="node-badge {{ 'bg-red-100 text-red-700' if child.open_deviations_count > 0 else 'bg-green-100 text-green-700' }}">{{ child.deviations_count }}D</span>
                        {% endif %}
                        {% endif %}
                    </button>
                    {% endfor %}
                {% endfor %}
                </div>
            </div><!-- /#stepCol -->

            <!-- ═══ MIDDLE: Content area ═══ -->
            <div id="contentArea" class="flex-1 overflow-y-auto bg-slate-50">

                <!-- ── OVERVIEW PANEL (default) ── -->
                <div id="overviewPanel" class="max-w-4xl mx-auto px-6 py-8 space-y-6">

                    {% if max_cycle > 1 %}
                    <!-- Zyklus-Tabs -->
                    <div class="flex items-center gap-1.5">
                        <span class="text-[10px] font-bold uppercase tracking-wider text-slate-400 mr-1">Zyklus</span>
                        <button class="cycle-tab active-tab" data-cycle="0" onclick="switchCycle(0)">Alle</button>
                        {% for c in range(1, max_cycle + 1) %}
                        <button class="cycle-tab" data-cycle="{{ c }}" onclick="switchCycle({{ c }})">Zyklus {{ c }}</button>
                        {% endfor %}
                    </div>
                    {% endif %}

                    <!-- Stat chips -->
                    <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
                        <div class="stat-card">
                            <div class="flex items-center gap-2 text-blue-600 mb-3">
                                <span class="material-icons-outlined text-lg">inventory_2</span>
                                <span class="text-xs font-bold uppercase tracking-wider">Teile</span>
                            </div>
                            <p id="overviewPartsCount" class="text-3xl font-bold text-slate-900">{{ parts | rejectattr('nr') | list | length }}</p>
                            <p id="overviewPartsStatus" class="text-xs text-slate-500 mt-1">
                                {% set open_parts = parts | rejectattr('nr') | selectattr('checked', 'equalto', false) | list %}
                                {% if open_parts %}<span class="text-amber-600 font-semibold">{{ open_parts|length }} offen</span>{% else %}alle verbaut{% endif %}
                            </p>
                        </div>
                        <div class="stat-card">
                            <div class="flex items-center gap-2 mb-3 {{ 'text-red-500' if open_deviations > 0 else 'text-green-600' }}">
                                <span class="material-icons-outlined text-lg">report_problem</span>
                                <span class="text-xs font-bold uppercase tracking-wider">Deviations</span>
                            </div>
                            <p id="overviewDevsCount" class="text-3xl font-bold text-slate-900">{{ deviations|length }}</p>
                            <p id="overviewDevsStatus" class="text-xs text-slate-500 mt-1">
                                {% if open_deviations > 0 %}<span class="text-red-600 font-semibold">{{ open_deviations }} offen</span>{% else %}<span class="text-green-600 font-semibold">alle geschlossen</span>{% endif %}
                            </p>
                        </div>
                        <div class="stat-card opacity-50">
                            <div class="flex items-center gap-2 text-slate-300 mb-3">
                                <span class="material-icons-outlined text-lg">group</span>
                                <span class="text-xs font-bold uppercase tracking-wider">Team</span>
                            </div>
                            <p class="text-3xl font-bold text-slate-200">—</p>
                            <p class="text-xs text-slate-300 mt-1">Kommt bald</p>
                        </div>
                        <div class="stat-card opacity-50">
                            <div class="flex items-center gap-2 text-slate-300 mb-3">
                                <span class="material-icons-outlined text-lg">hub</span>
                                <span class="text-xs font-bold uppercase tracking-wider">Systeme</span>
                            </div>
                            <p class="text-3xl font-bold text-slate-200">—</p>
                            <p class="text-xs text-slate-300 mt-1">SAP · HUB · API</p>
                        </div>
                    </div>

                    <!-- Parts table -->
                    <section class="report-card overflow-hidden">
                        <div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
                            <div class="flex items-center gap-2">
                                <span class="material-icons-outlined text-blue-500 text-lg">inventory_2</span>
                                <h2 class="text-sm font-bold text-slate-800">Verwendete Teile</h2>
                                {% if slave_units %}
                                <span class="badge bg-amber-100 text-amber-700 border border-amber-200">{{ slave_units|length }} Slave Units</span>
                                {% endif %}
                            </div>
                            <div class="flex items-center gap-2">
                                <span class="badge badge-blue">{{ parts | rejectattr('nr') | list | length }}</span>
                                <button onclick="openSlaveUnitsModal()" title="Slave Units hinzufügen" class="flex items-center gap-1 px-2 py-1 text-xs font-medium text-amber-600 hover:bg-amber-50 rounded-md border border-amber-300 transition-colors">
                                    <span class="material-icons-outlined text-[14px]">add</span>
                                </button>
                            </div>
                        </div>
                        {% if parts %}
                        <div class="overflow-x-auto">
                            <table class="data-table" id="partsSelectionTable">
                                <thead><tr>
                                    <th style="width:3%" class="text-center"><input type="checkbox" id="selectAllParts" class="rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-4 w-4 cursor-pointer" title="Alle auswählen"></th>
                                    {% if max_cycle > 1 %}<th class="cycle-col-cell" style="width:7%">Zyklus</th>{% endif %}
                                    <th style="width:{% if max_cycle > 1 %}16{% else %}19{% endif %}%">Schritt</th>
                                    <th style="width:{% if max_cycle > 1 %}24{% else %}27{% endif %}%">Beschreibung</th>
                                    <th style="width:{% if max_cycle > 1 %}23{% else %}25{% endif %}%">Teil</th>
                                    <th style="width:{% if max_cycle > 1 %}13{% else %}14{% endif %}%">Von</th>
                                    <th style="width:{% if max_cycle > 1 %}14{% else %}12{% endif %}%">Datum</th>
                                </tr></thead>
                                <tbody>
                                {% for p in parts %}
                                <tr class="cycle-row part-selectable-row {% if p.nr %}opacity-40{% endif %}" data-cycle="{{ p.cycle_number }}"
                                    data-part='{"step_id":"{{ p.step_id }}","step_name":{{ p.step_name|tojson }},"cycle":{{ p.cycle_number }},"text":{{ p.text|tojson }},"part_label":{{ (p.part_label or "")|tojson }},"pn":{{ (p.pn or "")|tojson }},"completed_by":{{ (p.completed_by or "")|tojson }},"completed_at":{{ (p.completed_at or "")|tojson }},"nr":{{ "true" if p.nr else "false" }}}'>
                                    <td class="text-center"><input type="checkbox" class="part-row-cb rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-4 w-4 cursor-pointer" {% if p.nr %}disabled title="N/R – nicht auswählbar"{% endif %}></td>
                                    {% if max_cycle > 1 %}<td class="cycle-col-cell"><span class="badge badge-blue" style="font-size:0.65rem">Z{{ p.cycle_number }}</span></td>{% endif %}
                                    <td><span class="font-mono text-[11px] text-slate-400">{{ p.step_name }}</span></td>
                                    <td class="font-medium {% if p.nr %}text-slate-400 line-through{% else %}text-slate-700{% endif %}">{{ p.text }}</td>
                                    <td>
                                        {% if p.nr %}<span class="text-slate-400 italic text-xs">N/R</span>
                                        {% elif p.part_label %}
                                            <span class="font-semibold text-slate-800">{{ p.part_label }}</span>
                                            {% if p.pn %}<span class="block text-[11px] text-slate-500 font-mono">PN: {{ p.pn }}</span>{% endif %}
                                            {% if p.sn %}<span class="block text-[11px] text-slate-400 font-mono">SN: {{ p.sn }}</span>{% endif %}
                                        {% else %}<span class="text-slate-300 italic text-xs">— offen —</span>
                                        {% endif %}
                                    </td>
                                    <td class="text-slate-500">{{ p.completed_by or '—' }}</td>
                                    <td class="text-slate-400 text-xs font-mono">{{ p.completed_at or '—' }}</td>
                                </tr>
                                {% endfor %}
                                {% for su in slave_units %}
                                <tr class="bg-amber-50/50 part-selectable-row group/su" data-cycle="1"
                                    data-part='{"step_id":"slave_units","step_name":"Slave Unit","cycle":1,"text":"","part_label":{{ (su.name or "")|tojson }},"pn":{{ (su.pn or "")|tojson }},"sn":{{ (su.sn or "")|tojson }},"completed_by":"","completed_at":"","nr":false}'>
                                    <td class="text-center"><input type="checkbox" class="part-row-cb rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-4 w-4 cursor-pointer"></td>
                                    {% if max_cycle > 1 %}<td class="cycle-col-cell"></td>{% endif %}
                                    <td><span class="inline-flex items-center gap-1 text-[10px] font-semibold uppercase tracking-wide text-amber-700 bg-amber-100 px-1.5 py-0.5 rounded">Slave Unit</span></td>
                                    <td></td>
                                    <td>
                                        {% if su.name %}<span class="font-semibold text-slate-800">{{ su.name }}</span>{% endif %}
                                        {% if su.pn %}<span class="block text-[11px] text-slate-500 font-mono">PN: {{ su.pn }}</span>{% endif %}
                                        {% if su.sn %}<span class="block text-[11px] text-slate-400 font-mono">SN: {{ su.sn }}</span>{% endif %}
                                        {% if not su.name and not su.pn and not su.sn %}<span class="text-slate-300 italic text-xs">—</span>{% endif %}
                                    </td>
                                    <td class="text-slate-400 text-xs">—</td>
                                    <td class="text-right pr-2">
                                        <button onclick="deleteSlaveUnit({{ loop.index0 }})" title="Slave Unit entfernen"
                                            class="opacity-0 group-hover/su:opacity-100 transition-opacity p-1 rounded hover:bg-red-100 text-slate-300 hover:text-red-500">
                                            <span class="material-icons-outlined text-[15px]">delete</span>
                                        </button>
                                    </td>
                                </tr>
                                {% endfor %}
                                </tbody>
                            </table>
                        </div>
                        {% elif slave_units %}
                        <div class="overflow-x-auto">
                            <table class="data-table" id="partsSelectionTable">
                                <thead><tr>
                                    <th style="width:3%" class="text-center"></th>
                                    <th style="width:19%">Schritt</th>
                                    <th style="width:27%">Beschreibung</th>
                                    <th style="width:25%">Teil</th>
                                    <th style="width:14%">Von</th>
                                    <th style="width:9%">Datum</th>
                                    <th style="width:3%"></th>
                                </tr></thead>
                                <tbody>
                                {% for su in slave_units %}
                                <tr class="bg-amber-50/50 part-selectable-row group/su" data-cycle="1"
                                    data-part='{"step_id":"slave_units","step_name":"Slave Unit","cycle":1,"text":"","part_label":{{ (su.name or "")|tojson }},"pn":{{ (su.pn or "")|tojson }},"sn":{{ (su.sn or "")|tojson }},"completed_by":"","completed_at":"","nr":false}'>
                                    <td class="text-center"><input type="checkbox" class="part-row-cb rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-4 w-4 cursor-pointer"></td>
                                    <td><span class="inline-flex items-center gap-1 text-[10px] font-semibold uppercase tracking-wide text-amber-700 bg-amber-100 px-1.5 py-0.5 rounded">Slave Unit</span></td>
                                    <td></td>
                                    <td>
                                        {% if su.name %}<span class="font-semibold text-slate-800">{{ su.name }}</span>{% endif %}
                                        {% if su.pn %}<span class="block text-[11px] text-slate-500 font-mono">PN: {{ su.pn }}</span>{% endif %}
                                        {% if su.sn %}<span class="block text-[11px] text-slate-400 font-mono">SN: {{ su.sn }}</span>{% endif %}
                                        {% if not su.name and not su.pn and not su.sn %}<span class="text-slate-300 italic text-xs">—</span>{% endif %}
                                    </td>
                                    <td class="text-slate-400 text-xs">—</td>
                                    <td class="text-slate-400 text-xs">—</td>
                                    <td class="text-right pr-2">
                                        <button onclick="deleteSlaveUnit({{ loop.index0 }})" title="Slave Unit entfernen"
                                            class="opacity-0 group-hover/su:opacity-100 transition-opacity p-1 rounded hover:bg-red-100 text-slate-300 hover:text-red-500">
                                            <span class="material-icons-outlined text-[15px]">delete</span>
                                        </button>
                                    </td>
                                </tr>
                                {% endfor %}
                                </tbody>
                            </table>
                        </div>
                        {% else %}
                        <div class="py-12 text-center">
                            <span class="material-icons-outlined text-slate-200 text-4xl">inventory_2</span>
                            <p class="text-slate-400 text-sm mt-2">Noch keine Part Selections abgeschlossen.</p>
                        </div>
                        {% endif %}
                    </section>

                    <!-- Parts Selection Action Bar -->
                    <div id="partsActionBar" class="hidden fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex items-center gap-3 bg-slate-900 text-white px-5 py-3 rounded-2xl shadow-2xl shadow-slate-900/40 border border-slate-700 animate-slide-up">
                        <span class="material-icons-outlined text-blue-400 text-lg">inventory_2</span>
                        <span id="partsActionCount" class="text-sm font-semibold tabular-nums">0 Teile ausgewählt</span>
                        <div class="w-px h-5 bg-slate-700"></div>
                        <button onclick="sendSelectedParts()" class="flex items-center gap-1.5 px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold rounded-lg transition-colors">
                            <span class="material-icons-outlined text-[15px]">send</span> Weiterleiten
                        </button>
                        <button onclick="clearPartSelection()" class="flex items-center gap-1.5 px-2 py-1.5 text-slate-400 hover:text-white text-xs font-medium rounded-lg transition-colors" title="Auswahl aufheben">
                            <span class="material-icons-outlined text-[15px]">close</span>
                        </button>
                    </div>

                    <!-- Deviations table -->
                    <section class="report-card overflow-hidden">
                        <div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
                            <div class="flex items-center gap-2">
                                <span class="material-icons-outlined text-lg {{ 'text-red-500' if open_deviations > 0 else 'text-green-500' }}">report_problem</span>
                                <h2 class="text-sm font-bold text-slate-800">Deviations</h2>
                            </div>
                            <div class="flex gap-2">
                                {% if open_deviations > 0 %}<span class="badge badge-red">{{ open_deviations }} offen</span>{% endif %}
                                <span class="badge badge-gray">{{ deviations|length }} gesamt</span>
                            </div>
                        </div>
                        {% if deviations %}
                        <div class="overflow-x-auto">
                            <table class="data-table">
                                <thead><tr>
                                    {% if max_cycle > 1 %}<th class="cycle-col-cell" style="width:7%">Zyklus</th>{% endif %}
                                    <th style="width:{% if max_cycle > 1 %}14{% else %}18{% endif %}%">Schritt</th>
                                    <th style="width:{% if max_cycle > 1 %}28{% else %}33{% endif %}%">Grund</th>
                                    <th style="width:{% if max_cycle > 1 %}12{% else %}13{% endif %}%">Status</th>
                                    <th style="width:{% if max_cycle > 1 %}14{% else %}16{% endif %}%">HUB-Nr.</th>
                                    <th style="width:{% if max_cycle > 1 %}8{% else %}9{% endif %}%">Typ</th>
                                    <th style="width:{% if max_cycle > 1 %}10{% else %}11{% endif %}%" class="text-center" title="Lessons Learned">L/L</th>
                                </tr></thead>
                                <tbody>
                                {% for d in deviations %}
                                <tr class="ll-dev-row cycle-row" data-step-id="{{ d.step_id }}" data-dev-index="{{ d.dev_index }}" data-cycle="{{ d.cycle_number }}">
                                    {% if max_cycle > 1 %}<td class="cycle-col-cell"><span class="badge badge-blue" style="font-size:0.65rem">Z{{ d.cycle_number }}</span></td>{% endif %}
                                    <td><span class="font-mono text-[11px] text-slate-400">{{ d.step_name }}</span></td>
                                    <td class="font-medium text-slate-700">{{ d.reason or '—' }}</td>
                                    <td>{% if d.status == 'Open' %}<span class="badge badge-red">Offen</span>{% else %}<span class="badge badge-green">Closed</span>{% endif %}</td>
                                    <td>{% if d.hub_incident_number %}<span class="font-mono text-xs text-blue-700 font-semibold">{{ d.hub_incident_number }}</span>{% else %}<span class="text-slate-300 text-xs italic">—</span>{% endif %}</td>
                                    <td class="text-slate-500 text-xs">{{ d.hub_incident_type or '—' }}</td>
                                    <td class="text-center">
                                        <div class="ll-chip-container inline-flex flex-col items-center gap-1">
                                            {% if d.ll_status == 'yes' %}
                                            {% set ss = d.ll_send_status %}
                                            {% if ss == 'sent' %}{% set rCls = 'bg-green-100 text-green-700 hover:bg-green-200' %}
                                            {% elif ss == 'in_progress' %}{% set rCls = 'bg-blue-100 text-blue-600 hover:bg-blue-200' %}
                                            {% else %}{% set rCls = 'bg-slate-100 text-slate-400 hover:bg-slate-200' %}{% endif %}
                                            <div class="inline-flex rounded-lg border border-amber-300 overflow-hidden">
                                                <button type="button" class="ll-chip-btn px-1.5 py-1 flex items-center gap-0.5 border-r border-amber-300 bg-amber-100 text-amber-700 hover:bg-amber-200 transition-colors" data-ll-status="yes">
                                                    <span class="ll-chip-icon material-icons-outlined text-[13px]">school</span>
                                                    <span class="material-icons-outlined text-[13px]">arrow_drop_down</span>
                                                </button>
                                                <button type="button" class="ll-open-drawer-btn px-1.5 py-1 flex items-center {{ rCls }} transition-colors" title="LL aufarbeiten">
                                                    <span class="material-icons-outlined text-[13px]">edit_note</span>
                                                </button>
                                            </div>
                                            {% elif d.ll_status == 'no' %}
                                            <button type="button" class="ll-chip-btn inline-flex items-center gap-0.5 px-1.5 py-1 rounded-lg border transition-colors bg-slate-100 text-slate-500 border-slate-300 hover:bg-slate-200" data-ll-status="no">
                                                <span class="ll-chip-icon material-icons-outlined text-[13px]">check</span>
                                                <span class="material-icons-outlined text-[13px]">arrow_drop_down</span>
                                            </button>
                                            {% else %}
                                            <button type="button" class="ll-chip-btn inline-flex items-center gap-0.5 px-1.5 py-1 rounded-lg border transition-colors text-slate-300 border-slate-200 border-dashed hover:text-slate-500 hover:border-slate-400 hover:bg-slate-50" data-ll-status="">
                                                <span class="ll-chip-icon material-icons-outlined text-[13px]">radio_button_unchecked</span>
                                                <span class="material-icons-outlined text-[13px]">arrow_drop_down</span>
                                            </button>
                                            {% endif %}
                                        </div>
                                    </td>
                                </tr>
                                {% endfor %}
                                </tbody>
                            </table>
                        </div>
                        {% else %}
                        <div class="py-12 text-center">
                            <span class="material-icons-outlined text-slate-200 text-4xl">check_circle</span>
                            <p class="text-slate-400 text-sm mt-2">Keine Deviations erfasst.</p>
                        </div>
                        {% endif %}
                    </section>

                    <!-- Verwendete Manuals -->
                    <section class="report-card overflow-hidden" id="manualsSection">
                        <div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
                            <div class="flex items-center gap-2">
                                <span class="material-icons-outlined text-teal-500 text-lg">menu_book</span>
                                <h2 class="text-sm font-bold text-slate-800">Verwendete Manuals</h2>
                            </div>
                            <span id="manualsSavedBadge" class="badge badge-gray hidden">gespeichert</span>
                        </div>
                        <div id="manualsChecklistWrap" class="px-5 py-4">
                            <p class="text-xs text-slate-400">Wird geladen…</p>
                        </div>
                    </section>

                    <!-- Placeholder row -->
                    <div class="grid grid-cols-2 gap-4">
                        <div class="report-card-placeholder p-6">
                            <span class="material-icons-outlined text-3xl text-slate-200">group</span>
                            <p class="text-sm font-semibold text-slate-300">Team & Zuweisungen</p>
                        </div>
                        <div class="report-card-placeholder p-6">
                            <span class="material-icons-outlined text-3xl text-slate-200">attach_file</span>
                            <p class="text-sm font-semibold text-slate-300">Dokumente & Anhänge</p>
                        </div>
                    </div>

                    <div class="report-card-placeholder p-10">
                        <span class="material-icons-outlined text-4xl text-slate-200">hub</span>
                        <p class="text-sm font-semibold text-slate-300">Externe Systeme</p>
                        <p class="text-xs text-slate-300">SAP · HUB · weitere API-Integrationen</p>
                    </div>

                    <div class="report-card-placeholder p-12">
                        <span class="material-icons-outlined text-4xl text-slate-200">timeline</span>
                        <p class="text-sm font-semibold text-slate-300">Projektverlauf / Zeitstrahl</p>
                    </div>

                    <div class="h-8"></div>
                </div><!-- /#overviewPanel -->

                <!-- ── STEP DETAIL PANEL (JS rendered) ── -->
                <div id="stepPanel" class="hidden max-w-4xl mx-auto px-6 py-8 space-y-6"></div>

            </div><!-- /#contentArea -->

        </div><!-- /.flex 3col -->
    </div><!-- /.flex-1 right side -->

<script>
const ALL_PARTS = {{ parts_json | safe }};
const ALL_DEVS  = {{ deviations_json | safe }};
const REPORT_PROJECT_ID = encodeURIComponent('{{ project.get("project_code","") }}_{{ project.get("engine_serial","") }}');
const REPORT_PROJECT_UUID = '{{ project_uuid }}';
const MAX_CYCLE = {{ max_cycle }};
let activeCycle = 0;

// ── Manual selection ──────────────────────────────────────────────────────────
let _manualCatalog = [];
let _savedManualIds = new Set();
let _manualSaveTimer = null;

async function initManuals() {
    const wrap = document.getElementById('manualsChecklistWrap');
    try {
        const [catRes, savedRes] = await Promise.all([
            fetch('/api/manual_catalog'),
            fetch(`/api/project/${REPORT_PROJECT_UUID}/manuals`)
        ]);
        _manualCatalog = catRes.ok ? await catRes.json() : [];
        const saved = savedRes.ok ? await savedRes.json() : { manuals: [] };
        _savedManualIds = new Set((saved.manuals || []).map(m => m.id));
    } catch (e) {
        _manualCatalog = [];
    }
    renderManualsChecklist(wrap);
}

function renderManualsChecklist(wrap) {
    if (_manualCatalog.length === 0) {
        wrap.innerHTML = '<p class="text-xs text-slate-400">Kein Manual-Katalog gepflegt. Bitte unter Admin → Process Templates → Manuals anlegen.</p>';
        return;
    }
    const selectedHtml = _manualCatalog.filter(m => _savedManualIds.has(m.id));
    const unselectedHtml = _manualCatalog.filter(m => !_savedManualIds.has(m.id));
    const rows = [...selectedHtml, ...unselectedHtml].map(m => {
        const checked = _savedManualIds.has(m.id) ? 'checked' : '';
        return `<label class="flex items-start gap-3 py-2.5 border-b border-slate-50 last:border-0 cursor-pointer hover:bg-slate-50 -mx-5 px-5 rounded transition-colors">
            <input type="checkbox" class="manual-cb mt-0.5 h-4 w-4 rounded border-slate-300 text-teal-600 focus:ring-teal-500 flex-shrink-0" data-id="${m.id}" ${checked}>
            <div class="flex-1 min-w-0">
                <span class="text-sm font-medium text-slate-800">${m.manualname || '—'}</span>
                <span class="ml-2 text-xs font-mono text-slate-400">${m.referenzcode || ''}</span>
                ${m.revision ? `<span class="ml-1 badge badge-blue" style="font-size:0.65rem">Rev. ${m.revision}</span>` : ''}
                ${m.datum ? `<span class="ml-1 text-xs text-slate-400">${m.datum}</span>` : ''}
                ${m.engine_subtype ? `<span class="block text-xs text-slate-400 mt-0.5">${m.engine_subtype}</span>` : ''}
            </div>
        </label>`;
    }).join('');
    wrap.innerHTML = `<div class="divide-y divide-slate-50">${rows}</div>`;
    wrap.querySelectorAll('.manual-cb').forEach(cb => {
        cb.addEventListener('change', () => {
            if (cb.checked) _savedManualIds.add(cb.dataset.id);
            else _savedManualIds.delete(cb.dataset.id);
            scheduleManualsave();
        });
    });
}

function scheduleManualsave() {
    clearTimeout(_manualSaveTimer);
    _manualSaveTimer = setTimeout(saveManuals, 600);
}

async function saveManuals() {
    const snapshot = _manualCatalog
        .filter(m => _savedManualIds.has(m.id))
        .map(m => ({ ...m }));
    try {
        await fetch(`/api/project/${REPORT_PROJECT_UUID}/manuals`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ manuals: snapshot })
        });
        const badge = document.getElementById('manualsSavedBadge');
        if (badge) {
            badge.classList.remove('hidden');
            setTimeout(() => badge.classList.add('hidden'), 2000);
        }
    } catch (e) { console.error('Manuals save failed', e); }
}

document.addEventListener('DOMContentLoaded', initManuals);

function openSlaveUnitsModal() {
    document.getElementById('slaveUnitsModal').classList.remove('hidden');
    const hasUnits = {{ 'true' if slave_units else 'false' }};
    const deleteBtn = document.getElementById('slaveUnitsDeleteAllBtn');
    if (deleteBtn) deleteBtn.classList.toggle('hidden', !hasUnits);
}
function closeSlaveUnitsModal() {
    document.getElementById('slaveUnitsModal').classList.add('hidden');
}
document.addEventListener('keydown', e => {
    if (e.key === 'Escape') closeSlaveUnitsModal();
});

async function uploadSlaveUnits(input) {
    const file = input.files[0];
    if (!file) return;
    const nameEl = document.getElementById('slaveUnitsFileName');
    if (nameEl) { nameEl.textContent = file.name; nameEl.classList.remove('hidden'); }
    const fd = new FormData();
    fd.append('file', file);
    input.value = '';
    try {
        const res = await fetch(`/api/project/${REPORT_PROJECT_UUID}/slave_units`, { method: 'POST', body: fd });
        const data = await res.json();
        if (!res.ok) { alert('Fehler: ' + (data.error || res.status)); return; }
        location.reload();
    } catch(e) { alert('Upload fehlgeschlagen: ' + e); }
}

async function addSlaveUnit() {
    const name = document.getElementById('suInputName').value.trim();
    const pn   = document.getElementById('suInputPn').value.trim();
    const sn   = document.getElementById('suInputSn').value.trim();
    const errEl = document.getElementById('suAddError');
    if (!name && !pn && !sn) {
        errEl.textContent = 'Mindestens ein Feld muss ausgefüllt sein.';
        errEl.classList.remove('hidden');
        return;
    }
    errEl.classList.add('hidden');
    try {
        const res = await fetch(`/api/project/${REPORT_PROJECT_UUID}/slave_units/add`, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({name, pn, sn})
        });
        const data = await res.json();
        if (!res.ok) { errEl.textContent = data.error || 'Fehler'; errEl.classList.remove('hidden'); return; }
        location.reload();
    } catch(e) { errEl.textContent = 'Fehler: ' + e; errEl.classList.remove('hidden'); }
}

async function deleteSlaveUnit(index) {
    try {
        const res = await fetch(`/api/project/${REPORT_PROJECT_UUID}/slave_units/${index}`, { method: 'DELETE' });
        const data = await res.json();
        if (!res.ok) { alert('Fehler: ' + (data.error || res.status)); return; }
        location.reload();
    } catch(e) { alert('Fehler: ' + e); }
}

async function deleteSlaveUnits() {
    if (!confirm('Alle Slave Units wirklich entfernen?')) return;
    try {
        const res = await fetch(`/api/project/${REPORT_PROJECT_UUID}/slave_units`, { method: 'DELETE' });
        const data = await res.json();
        if (!res.ok) { alert('Fehler: ' + (data.error || res.status)); return; }
        location.reload();
    } catch(e) { alert('Fehler: ' + e); }
}

function switchCycle(n) {
    activeCycle = n;
    document.querySelectorAll('.cycle-tab').forEach(btn => {
        btn.classList.toggle('active-tab', parseInt(btn.dataset.cycle) === n);
    });
    document.querySelectorAll('.cycle-row').forEach(row => {
        const rc = parseInt(row.dataset.cycle) || 1;
        row.style.display = (n === 0 || rc === n) ? '' : 'none';
    });
    document.querySelectorAll('.cycle-col-cell').forEach(el => {
        el.style.display = (n === 0) ? '' : 'none';
    });
    document.querySelectorAll('.cycle-group-header').forEach(hdr => {
        const hc = parseInt(hdr.dataset.cycle);
        hdr.style.display = (n === 0 || hc === n) ? '' : 'none';
    });
    document.querySelectorAll('.step-btn[data-cycle]').forEach(btn => {
        const bc = parseInt(btn.dataset.cycle);
        btn.style.display = (n === 0 || bc === n) ? '' : 'none';
    });
    _updateOverviewStats(n);
}

function _updateOverviewStats(n) {
    const fp = n === 0 ? ALL_PARTS : ALL_PARTS.filter(p => (p.cycle_number || 1) === n);
    const fd = n === 0 ? ALL_DEVS  : ALL_DEVS.filter(d => (d.cycle_number || 1) === n);
    const openP = fp.filter(p => !p.nr && !p.checked).length;
    const openD = fd.filter(d => d.status === 'Open').length;
    const pc = document.getElementById('overviewPartsCount');
    const ps = document.getElementById('overviewPartsStatus');
    const dc = document.getElementById('overviewDevsCount');
    const ds = document.getElementById('overviewDevsStatus');
    if (pc) pc.textContent = fp.filter(p => !p.nr).length;
    if (ps) ps.innerHTML = openP > 0 ? `<span class="text-amber-600 font-semibold">${openP} offen</span>` : 'alle verbaut';
    if (dc) dc.textContent = fd.length;
    if (ds) ds.innerHTML = openD > 0 ? `<span class="text-red-600 font-semibold">${openD} offen</span>` : '<span class="text-green-600 font-semibold">alle geschlossen</span>';
}

(function initLessonsLearned() {
    const PATCH_URL = `/api/project_status/${REPORT_PROJECT_ID}/deviation`;
    let _chipCleanup = null;

    async function patchDev(stepId, devIndex, patch) {
        try {
            await fetch(PATCH_URL, {
                method: 'PATCH',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({ step_id_str: stepId, dev_index: devIndex, patch })
            });
        } catch(e) { console.error('LL patch error:', e); }
    }

    function closeChipDropdown() {
        const d = document.getElementById('llChipDropdown');
        if (d) d.remove();
        if (_chipCleanup) { document.removeEventListener('click', _chipCleanup); _chipCleanup = null; }
    }

    function getSendStatus(stepId, devIndex) {
        const dev = ALL_DEVS.find(d => d.step_id === stepId && d.dev_index === devIndex);
        return dev ? (dev.ll_send_status || '') : '';
    }

    window.applyChipState = function applyChipState(container, stepId, devIndex, status) {
        const ss = getSendStatus(stepId, devIndex);
        const sent = ss === 'sent', inprog = ss === 'in_progress';
        if (status === 'yes') {
            const rCls = sent ? 'bg-green-100 text-green-700 hover:bg-green-200' : inprog ? 'bg-blue-100 text-blue-600 hover:bg-blue-200' : 'bg-slate-100 text-slate-400 hover:bg-slate-200';
            container.innerHTML = `<div class="inline-flex rounded-lg border border-amber-300 overflow-hidden"><button class="ll-chip-btn px-1.5 py-1 flex items-center gap-0.5 border-r border-amber-300 bg-amber-100 text-amber-700 hover:bg-amber-200 transition-colors" data-ll-status="yes"><span class="ll-chip-icon material-icons-outlined text-[13px]">school</span><span class="material-icons-outlined text-[13px]">arrow_drop_down</span></button><button class="ll-open-drawer-btn px-1.5 py-1 flex items-center ${rCls} transition-colors" title="LL aufarbeiten"><span class="material-icons-outlined text-[13px]">edit_note</span></button></div>`;
        } else if (status === 'no') {
            container.innerHTML = `<button class="ll-chip-btn inline-flex items-center gap-0.5 px-1.5 py-1 rounded-lg border transition-colors bg-slate-100 text-slate-500 border-slate-300 hover:bg-slate-200" data-ll-status="no"><span class="ll-chip-icon material-icons-outlined text-[13px]">check</span><span class="material-icons-outlined text-[13px]">arrow_drop_down</span></button>`;
        } else {
            container.innerHTML = `<button class="ll-chip-btn inline-flex items-center gap-0.5 px-1.5 py-1 rounded-lg border transition-colors text-slate-300 border-slate-200 border-dashed hover:text-slate-500 hover:border-slate-400 hover:bg-slate-50" data-ll-status=""><span class="ll-chip-icon material-icons-outlined text-[13px]">radio_button_unchecked</span><span class="material-icons-outlined text-[13px]">arrow_drop_down</span></button>`;
        }
        rebindChipEvents(container, stepId, devIndex);
    }

    function rebindChipEvents(container, stepId, devIndex) {
        const chip = container.querySelector('.ll-chip-btn');
        if (chip) chip.addEventListener('click', e => { e.stopPropagation(); openChipDropdown(chip, container, stepId, devIndex); });
        const drawerBtn = container.querySelector('.ll-open-drawer-btn');
        if (drawerBtn) drawerBtn.addEventListener('click', e => { e.stopPropagation(); openLLDrawer(stepId, devIndex); });
    }

    function openChipDropdown(chip, container, stepId, devIndex) {
        closeChipDropdown();
        const current = chip.dataset.llStatus || '';
        const items = [
            { status: 'yes', icon: 'school',               label: 'Lessons Learned',   cls: 'text-amber-700' },
            { status: 'no',  icon: 'check',                label: 'Kein LL (geprüft)', cls: 'text-slate-600' },
            { status: '',    icon: 'radio_button_unchecked',label: 'Zurücksetzen',       cls: 'text-slate-400' },
        ];
        const dropdown = document.createElement('div');
        dropdown.id = 'llChipDropdown';
        dropdown.style.cssText = 'position:fixed;z-index:9999;background:white;border:1px solid #e2e8f0;border-radius:0.5rem;box-shadow:0 10px 40px -8px rgba(0,0,0,0.18),0 2px 8px -2px rgba(0,0,0,0.08);padding:4px;min-width:160px;';
        dropdown.innerHTML = items.map(item => `
            <button data-status="${item.status}" class="flex items-center gap-2 w-full px-3 py-1.5 text-left text-[13px] font-medium rounded-md transition-colors hover:bg-slate-50 ${item.cls} ${item.status === current ? 'bg-slate-50 font-semibold' : ''}">
                <span class="material-icons-outlined text-[15px]">${item.icon}</span>${item.label}
                ${item.status === current ? '<span class="material-icons-outlined text-[13px] ml-auto">check</span>' : ''}
            </button>`).join('<hr style="border:none;border-top:1px solid #f1f5f9;margin:3px 0">');
        document.body.appendChild(dropdown);
        const rect = chip.getBoundingClientRect();
        const dropH = 3 * 36 + 2 * 7 + 8;
        const top = (window.innerHeight - rect.bottom > dropH) ? rect.bottom + 4 : rect.top - dropH - 4;
        let left = rect.left;
        if (left + 170 > window.innerWidth) left = window.innerWidth - 174;
        dropdown.style.top = top + 'px';
        dropdown.style.left = left + 'px';
        dropdown.querySelectorAll('button[data-status]').forEach(btn => {
            btn.addEventListener('click', () => {
                const newStatus = btn.dataset.status;
                const dev = ALL_DEVS.find(d => d.step_id === stepId && d.dev_index === devIndex);
                if (dev) { dev.ll_status = newStatus; dev.ll = newStatus === 'yes'; }
                applyChipState(container, stepId, devIndex, newStatus);
                patchDev(stepId, devIndex, { ll_status: newStatus, ll: newStatus === 'yes' });
                closeChipDropdown();
            });
        });
        _chipCleanup = (e) => { if (!dropdown.contains(e.target)) closeChipDropdown(); };
        setTimeout(() => document.addEventListener('click', _chipCleanup), 0);
    }

    document.querySelectorAll('.ll-dev-row').forEach(row => {
        const stepId   = row.dataset.stepId;
        const devIndex = parseInt(row.dataset.devIndex, 10);
        const container = row.querySelector('.ll-chip-container');
        if (container) rebindChipEvents(container, stepId, devIndex);
    });
})();

function statusBadge(status) {
    const map = {
        'Completed': 'badge-green', 'In Progress': 'badge-blue',
        'On Hold': 'badge-yellow', 'Upcoming': 'badge-gray'
    };
    return `<span class="badge ${map[status] || 'badge-gray'}">${status || '—'}</span>`;
}

function partsTableHTML(rows) {
    if (!rows.length) return `<div class="py-12 text-center"><span class="material-icons-outlined text-slate-200 text-4xl">inventory_2</span><p class="text-slate-400 text-sm mt-2">Keine Part Selections für diesen Schritt.</p></div>`;
    return `<div class="overflow-x-auto"><table class="data-table">
        <thead><tr>
            <th style="width:30%">Beschreibung</th>
            <th style="width:34%">Teil</th>
            <th style="width:18%">Von</th>
            <th style="width:18%">Datum</th>
        </tr></thead>
        <tbody>${rows.map(p => {
            const teilCell = p.nr
                ? '<span class="text-slate-400 italic text-xs">N/R</span>'
                : p.part_label
                    ? `<span class="font-semibold text-slate-800">${p.part_label}</span>${p.pn ? `<span class="block text-[11px] text-slate-500 font-mono">PN: ${p.pn}</span>` : ''}${p.sn ? `<span class="block text-[11px] text-slate-400 font-mono">SN: ${p.sn}</span>` : ''}`
                    : '<span class="text-slate-300 italic text-xs">— offen —</span>';
            return `<tr class="${p.nr ? 'opacity-40' : ''}">
            <td class="font-medium ${p.nr ? 'text-slate-400 line-through' : 'text-slate-700'}">${p.text || '—'}</td>
            <td>${teilCell}</td>
            <td class="text-slate-500">${p.completed_by || '—'}</td>
            <td class="text-slate-400 text-xs font-mono">${p.completed_at || '—'}</td>
        </tr>`;}).join('')}</tbody>
    </table></div>`;
}

function devsTableHTML(rows) {
    if (!rows.length) return `<div class="py-12 text-center"><span class="material-icons-outlined text-slate-200 text-4xl">check_circle</span><p class="text-slate-400 text-sm mt-2">Keine Deviations für diesen Schritt.</p></div>`;
    return `<div class="overflow-x-auto"><table class="data-table">
        <thead><tr>
            <th style="width:42%">Grund</th>
            <th style="width:16%">Status</th>
            <th style="width:24%">HUB-Nr.</th>
            <th style="width:18%">Typ</th>
        </tr></thead>
        <tbody>${rows.map(d => `<tr>
            <td class="font-medium text-slate-700">${d.reason || '—'}</td>
            <td>${d.status === 'Open' ? '<span class="badge badge-red">Offen</span>' : '<span class="badge badge-green">Closed</span>'}</td>
            <td>${d.hub_incident_number ? `<span class="font-mono text-xs text-blue-700 font-semibold">${d.hub_incident_number}</span>` : '<span class="text-slate-300 text-xs italic">—</span>'}</td>
            <td class="text-slate-500 text-xs">${d.hub_incident_type || '—'}</td>
        </tr>`).join('')}</tbody>
    </table></div>`;
}

function selectStep(stepId, stepName, stepStatus) {
    document.querySelectorAll('.step-btn').forEach(b => b.classList.remove('selected'));
    const btn = document.querySelector(`.step-btn[data-step-id="${stepId}"]`);
    if (btn) btn.classList.add('selected');

    document.getElementById('overviewPanel').classList.add('hidden');
    const panel = document.getElementById('stepPanel');
    panel.classList.remove('hidden');

    if (stepStatus === 'N/R') {
        panel.innerHTML = `
            <div class="report-card p-6">
                <div class="flex items-start justify-between gap-4">
                    <div>
                        <p class="text-[10px] uppercase tracking-widest text-slate-400 font-bold mb-1">Schritt</p>
                        <h2 class="text-xl font-bold text-slate-400 line-through">${stepName}</h2>
                    </div>
                    <span class="badge badge-gray">N/R</span>
                </div>
                <p class="text-sm text-slate-400 mt-4 flex items-center gap-2">
                    <span class="material-icons-outlined text-base">block</span>
                    Dieser Schritt wurde als <strong class="mx-1">Nicht benötigt</strong> markiert und enthält keine Einträge.
                </p>
            </div>`;
        return;
    }

    const parts = ALL_PARTS.filter(p => p.step_id === stepId);
    const activeParts = parts.filter(p => !p.nr);
    const devs  = ALL_DEVS.filter(d => d.step_id === stepId);
    const openDevs = devs.filter(d => d.status === 'Open').length;
    const openParts = activeParts.filter(p => !p.checked).length;

    panel.innerHTML = `
        <!-- Step header -->
        <div class="report-card p-6">
            <div class="flex items-start justify-between gap-4">
                <div>
                    <p class="text-[10px] uppercase tracking-widest text-slate-400 font-bold mb-1">Schritt</p>
                    <h2 class="text-xl font-bold text-slate-900">${stepName}</h2>
                </div>
                ${statusBadge(stepStatus)}
            </div>
        </div>

        <!-- Step stat chips -->
        <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
            <div class="stat-card">
                <div class="flex items-center gap-2 text-blue-600 mb-2"><span class="material-icons-outlined text-lg">inventory_2</span><span class="text-xs font-bold uppercase tracking-wider">Teile</span></div>
                <p class="text-3xl font-bold text-slate-900">${activeParts.length}</p>
                <p class="text-xs text-slate-500 mt-1">${openParts > 0 ? `<span class="text-amber-600 font-semibold">${openParts} offen</span>` : 'alle verbaut'}</p>
            </div>
            <div class="stat-card">
                <div class="flex items-center gap-2 mb-2 ${openDevs > 0 ? 'text-red-500' : 'text-green-600'}"><span class="material-icons-outlined text-lg">report_problem</span><span class="text-xs font-bold uppercase tracking-wider">Deviations</span></div>
                <p class="text-3xl font-bold text-slate-900">${devs.length}</p>
                <p class="text-xs text-slate-500 mt-1">${openDevs > 0 ? `<span class="text-red-600 font-semibold">${openDevs} offen</span>` : '<span class="text-green-600 font-semibold">alle geschlossen</span>'}</p>
            </div>
            <div class="stat-card opacity-50">
                <div class="flex items-center gap-2 text-slate-300 mb-2"><span class="material-icons-outlined text-lg">group</span><span class="text-xs font-bold uppercase tracking-wider">Team</span></div>
                <p class="text-3xl font-bold text-slate-200">—</p><p class="text-xs text-slate-300 mt-1">Kommt bald</p>
            </div>
            <div class="stat-card opacity-50">
                <div class="flex items-center gap-2 text-slate-300 mb-2"><span class="material-icons-outlined text-lg">schedule</span><span class="text-xs font-bold uppercase tracking-wider">Dauer</span></div>
                <p class="text-3xl font-bold text-slate-200">—</p><p class="text-xs text-slate-300 mt-1">Kommt bald</p>
            </div>
        </div>

        <!-- Parts -->
        <section class="report-card overflow-hidden">
            <div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
                <div class="flex items-center gap-2"><span class="material-icons-outlined text-blue-500 text-lg">inventory_2</span><h2 class="text-sm font-bold text-slate-800">Verwendete Teile</h2></div>
                <span class="badge badge-blue">${activeParts.length}</span>
            </div>
            ${partsTableHTML(parts)}
        </section>

        <!-- Deviations -->
        <section class="report-card overflow-hidden">
            <div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
                <div class="flex items-center gap-2"><span class="material-icons-outlined text-lg ${openDevs > 0 ? 'text-red-500' : 'text-green-500'}">report_problem</span><h2 class="text-sm font-bold text-slate-800">Deviations</h2></div>
                <div class="flex gap-2">${openDevs > 0 ? `<span class="badge badge-red">${openDevs} offen</span>` : ''}<span class="badge badge-gray">${devs.length} gesamt</span></div>
            </div>
            ${devsTableHTML(devs)}
        </section>

        <!-- API Nodes placeholder -->
        <div>
            <p class="text-xs font-bold uppercase tracking-wider text-slate-400 mb-3">Externe Schnittstellen</p>
            <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
                <div class="api-node"><span class="material-icons-outlined text-3xl text-slate-200">storage</span><p class="text-xs font-semibold text-slate-300">SAP</p><p class="text-[10px] text-slate-300">Materialwirtschaft</p></div>
                <div class="api-node"><span class="material-icons-outlined text-3xl text-slate-200">confirmation_number</span><p class="text-xs font-semibold text-slate-300">HUB</p><p class="text-[10px] text-slate-300">Incidents & Tickets</p></div>
                <div class="api-node"><span class="material-icons-outlined text-3xl text-slate-200">attach_file</span><p class="text-xs font-semibold text-slate-300">Dokumente</p><p class="text-[10px] text-slate-300">AMM, SRM, SB</p></div>
                <div class="api-node"><span class="material-icons-outlined text-3xl text-slate-200">add_circle_outline</span><p class="text-xs font-semibold text-slate-300">Weitere API</p><p class="text-[10px] text-slate-300">Konfigurierbar</p></div>
            </div>
        </div>

        <div class="h-8"></div>
    `;
}

function selectOverview() {
    document.querySelectorAll('.step-btn').forEach(b => b.classList.remove('selected'));
    document.getElementById('overviewBtn').classList.add('selected');
    document.getElementById('overviewPanel').classList.remove('hidden');
    document.getElementById('stepPanel').classList.add('hidden');
}

// Sidebar collapse
(function() {
    const sidebar = document.getElementById('sidebar');
    const btn = document.getElementById('sidebar-toggle');
    if (!sidebar || !btn) return;
    const icon = btn.querySelector('.material-icons-outlined');
    const apply = (collapsed) => {
        sidebar.classList.toggle('is-collapsed', collapsed);
        if (icon) icon.textContent = collapsed ? 'menu' : 'chevron_left';
    };
    btn.addEventListener('click', () => {
        const now = !sidebar.classList.contains('is-collapsed');
        localStorage.setItem('sidebarCollapsed', now);
        apply(now);
    });
    apply(localStorage.getItem('sidebarCollapsed') === 'true');
})();

// Left column resize
(function() {
    const col = document.getElementById('stepCol');
    const handle = document.getElementById('stepColResizer');
    if (!col || !handle) return;
    let dragging = false, startX = 0, startW = 0;
    handle.addEventListener('mousedown', e => {
        dragging = true; startX = e.clientX; startW = col.offsetWidth;
        col.classList.add('is-resizing');
        handle.classList.add('is-resizing');
        document.body.style.cursor = 'col-resize';
        document.body.style.userSelect = 'none';
        e.preventDefault();
    });
    document.addEventListener('mousemove', e => {
        if (!dragging) return;
        const w = Math.min(600, Math.max(200, startW + (e.clientX - startX)));
        col.style.width = w + 'px';
    });
    document.addEventListener('mouseup', () => {
        if (!dragging) return;
        dragging = false;
        col.classList.remove('is-resizing');
        handle.classList.remove('is-resizing');
        document.body.style.cursor = '';
        document.body.style.userSelect = '';
    });
})();

// --- Parts table row selection ---
(function initPartsSelection() {
    const table = document.getElementById('partsSelectionTable');
    if (!table) return;

    const actionBar   = document.getElementById('partsActionBar');
    const countLabel  = document.getElementById('partsActionCount');
    const selectAllCb = document.getElementById('selectAllParts');

    function getSelected() {
        return Array.from(table.querySelectorAll('.part-row-cb:checked:not(:disabled)'))
            .map(cb => JSON.parse(cb.closest('tr').dataset.part));
    }

    function updateBar() {
        const n = table.querySelectorAll('.part-row-cb:checked:not(:disabled)').length;
        if (n > 0) {
            countLabel.textContent = `${n} Teil${n > 1 ? 'e' : ''} ausgewählt`;
            actionBar.classList.remove('hidden');
        } else {
            actionBar.classList.add('hidden');
        }
        const total = table.querySelectorAll('.part-row-cb:not(:disabled)').length;
        selectAllCb.checked = n > 0 && n === total;
        selectAllCb.indeterminate = n > 0 && n < total;
    }

    table.addEventListener('change', e => {
        if (e.target.classList.contains('part-row-cb')) updateBar();
    });

    selectAllCb.addEventListener('change', () => {
        table.querySelectorAll('.part-row-cb:not(:disabled)').forEach(cb => {
            cb.checked = selectAllCb.checked;
        });
        updateBar();
    });

    window.clearPartSelection = function() {
        table.querySelectorAll('.part-row-cb').forEach(cb => cb.checked = false);
        selectAllCb.checked = false;
        selectAllCb.indeterminate = false;
        actionBar.classList.add('hidden');
    };

    window.sendSelectedParts = function() {
        const parts = getSelected();
        // Placeholder — Zertifikat-Logik kommt hier rein
        console.log('Ausgewählte Teile:', parts);
        alert(`${parts.length} Teil(e) bereit zur Weiterleitung.\n(Implementierung folgt)`);
    };
})();
</script>

<!-- LL Drawer Overlay -->
<div id="llDrawerOverlay" class="fixed inset-0 bg-black/30 z-40 hidden transition-opacity" onclick="closeLLDrawer()"></div>

<aside id="llDrawer" class="fixed top-0 right-0 h-full w-[580px] max-w-full bg-white shadow-2xl z-50 flex flex-col translate-x-full transition-transform duration-300 ease-in-out">
    <!-- Header -->
    <div class="flex items-center justify-between px-5 py-4 border-b border-slate-200 bg-amber-50 flex-shrink-0">
        <div class="flex items-center gap-2">
            <span class="material-icons-outlined text-amber-600">school</span>
            <div>
                <p class="text-xs font-bold text-amber-700 uppercase tracking-wider">Lessons Learned</p>
                <div class="flex items-center gap-2 mt-0.5">
                    <p id="llDrawerDevId" class="text-sm font-semibold text-slate-800"></p>
                    <button id="llSendStatusChip" class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-[11px] font-semibold transition-colors" onclick="openLLSendStatusDropdown(event)"></button>
                </div>
            </div>
        </div>
        <div class="flex items-center gap-2">
            <button id="llDrawerPrev" onclick="navigateLLDrawer(-1)" class="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium text-slate-600 hover:bg-slate-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">
                <span class="material-icons-outlined text-sm">chevron_left</span> Vorherige
            </button>
            <span id="llDrawerCounter" class="text-xs text-slate-400 font-mono"></span>
            <button id="llDrawerNext" onclick="navigateLLDrawer(1)" class="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium text-slate-600 hover:bg-slate-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">
                Nächste <span class="material-icons-outlined text-sm">chevron_right</span>
            </button>
            <button onclick="closeLLDrawer()" class="ml-1 p-1 rounded hover:bg-slate-200 text-slate-400 transition-colors">
                <span class="material-icons-outlined text-lg">close</span>
            </button>
        </div>
    </div>

    <div class="flex-1 overflow-y-auto">
        <!-- Context section -->
        <div class="px-5 py-4 border-b border-slate-100 bg-slate-50">
            <p class="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2">Kontext</p>
            <div class="space-y-2">
                <div class="flex gap-2 text-xs">
                    <span class="font-semibold text-slate-500 w-20 flex-shrink-0">Schritt</span>
                    <span id="llCtxStep" class="text-slate-700 font-mono"></span>
                </div>
                <div class="flex gap-2 text-xs">
                    <span class="font-semibold text-slate-500 w-20 flex-shrink-0">Grund</span>
                    <span id="llCtxReason" class="text-slate-800 font-medium"></span>
                </div>
                <div id="llCtxDetailsWrap" class="flex gap-2 text-xs hidden">
                    <span class="font-semibold text-slate-500 w-20 flex-shrink-0">Details</span>
                    <span id="llCtxDetails" class="text-slate-700 whitespace-pre-wrap"></span>
                </div>
                <div class="flex gap-2 text-xs">
                    <span class="font-semibold text-slate-500 w-20 flex-shrink-0">Gemeldet</span>
                    <span id="llCtxReported" class="text-slate-500"></span>
                </div>
            </div>
            <!-- Thread -->
            <div id="llCtxThread" class="mt-3 space-y-1.5"></div>
        </div>

        <!-- LL enrichment fields -->
        <div class="px-5 py-5 space-y-4">
            <p class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Aufbereitung</p>

            <div>
                <label class="block text-xs font-semibold text-slate-600 mb-1">Kernaussage</label>
                <input id="llFieldNote" type="text" maxlength="200" placeholder="Kurze Kernaussage für die LL-Datenbank…"
                    class="w-full border border-slate-300 rounded-lg text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400 bg-white">
            </div>
            <div>
                <label class="block text-xs font-semibold text-slate-600 mb-1">Ursache</label>
                <textarea id="llFieldCause" rows="2" placeholder="Was war die Grundursache?"
                    class="w-full border border-slate-300 rounded-lg text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400 bg-white resize-none"></textarea>
            </div>
            <div>
                <label class="block text-xs font-semibold text-slate-600 mb-1">Maßnahme</label>
                <textarea id="llFieldMeasure" rows="2" placeholder="Welche Maßnahme wurde ergriffen?"
                    class="w-full border border-slate-300 rounded-lg text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400 bg-white resize-none"></textarea>
            </div>
            <div>
                <label class="block text-xs font-semibold text-slate-600 mb-1">Empfehlung / Prevention</label>
                <textarea id="llFieldRecommendation" rows="2" placeholder="Was sollte in Zukunft beachtet werden?"
                    class="w-full border border-slate-300 rounded-lg text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400 bg-white resize-none"></textarea>
            </div>
            <div>
                <label class="block text-xs font-semibold text-slate-600 mb-1">Kategorie</label>
                <select id="llFieldCategory" class="w-full border border-slate-300 rounded-lg text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400 bg-white">
                    <option value="">— Kategorie wählen —</option>
                    <option value="Prozess">Prozess</option>
                    <option value="Hardware">Hardware</option>
                    <option value="Software">Software</option>
                    <option value="Sicherheit">Sicherheit</option>
                    <option value="Kommunikation">Kommunikation</option>
                    <option value="Planung">Planung</option>
                    <option value="Sonstiges">Sonstiges</option>
                </select>
            </div>
        </div>
    </div>

    <!-- Footer -->
    <div class="flex items-center justify-between px-5 py-3 border-t border-slate-200 bg-white flex-shrink-0">
        <span id="llDrawerSaveStatus" class="text-xs text-slate-400"></span>
        <div class="flex items-center gap-2">
            <button onclick="saveLLDrawer()" class="flex items-center gap-1.5 px-3 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm font-semibold hover:bg-slate-200 transition-colors">
                <span class="material-icons-outlined text-base">save</span> Speichern
            </button>
            <button id="llSendBtn" onclick="sendLLToDb()" class="flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-semibold transition-colors shadow-sm bg-amber-500 text-white hover:bg-amber-600">
                <span class="material-icons-outlined text-base">send</span> An DB senden
            </button>
        </div>
    </div>
</aside>

<script>
(function initLLDrawer() {
    const PATCH_URL = `/api/project_status/${REPORT_PROJECT_ID}/deviation`;
    let llItems = [];
    let currentLLIndex = 0;

    function buildLLItems() {
        llItems = [];
        document.querySelectorAll('.ll-dev-row').forEach(row => {
            const chip = row.querySelector('.ll-chip-btn[data-ll-status="yes"]');
            if (chip) {
                llItems.push({
                    stepId:   row.dataset.stepId,
                    devIndex: parseInt(row.dataset.devIndex, 10),
                    row:      row
                });
            }
        });
    }

    const SEND_STATUS_CFG = {
        '':            { label: 'Offen',           icon: 'radio_button_unchecked', cls: 'bg-slate-100 border-slate-300 text-slate-500' },
        'in_progress': { label: 'In Bearbeitung',  icon: 'edit',                   cls: 'bg-blue-100 border-blue-300 text-blue-700' },
        'sent':        { label: 'Gesendet',         icon: 'check_circle',           cls: 'bg-green-100 border-green-300 text-green-700' },
    };

    function renderSendStatusChip(sendStatus) {
        const cfg = SEND_STATUS_CFG[sendStatus] || SEND_STATUS_CFG[''];
        const chip = document.getElementById('llSendStatusChip');
        if (!chip) return;
        chip.className = `inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-[11px] font-semibold transition-colors ${cfg.cls}`;
        chip.innerHTML = `<span class="material-icons-outlined text-[12px]">${cfg.icon}</span>${cfg.label} <span class="material-icons-outlined text-[11px]">arrow_drop_down</span>`;
    }

    let _sendCleanup = null;
    window.openLLSendStatusDropdown = function(e) {
        e.stopPropagation();
        const existing = document.getElementById('llSendDropdown');
        if (existing) { existing.remove(); if (_sendCleanup) { document.removeEventListener('click', _sendCleanup); _sendCleanup = null; } return; }
        const item = llItems[currentLLIndex];
        if (!item) return;
        const dev = ALL_DEVS.find(d => d.step_id === item.stepId && d.dev_index === item.devIndex);
        const current = dev ? (dev.ll_send_status || '') : '';
        const dropdown = document.createElement('div');
        dropdown.id = 'llSendDropdown';
        dropdown.style.cssText = 'position:fixed;z-index:9999;background:white;border:1px solid #e2e8f0;border-radius:0.5rem;box-shadow:0 10px 40px -8px rgba(0,0,0,0.18);padding:4px;min-width:170px;';
        dropdown.innerHTML = Object.entries(SEND_STATUS_CFG).map(([val, cfg]) => `
            <button data-val="${val}" class="flex items-center gap-2 w-full px-3 py-1.5 text-left text-[13px] font-medium rounded-md hover:bg-slate-50 ${val === current ? 'bg-slate-50 font-semibold' : ''}">
                <span class="material-icons-outlined text-[15px] ${cfg.cls.includes('green') ? 'text-green-600' : cfg.cls.includes('blue') ? 'text-blue-600' : 'text-slate-400'}">${cfg.icon}</span>${cfg.label}
                ${val === current ? '<span class="material-icons-outlined text-[13px] ml-auto text-slate-400">check</span>' : ''}
            </button>`).join('');
        document.body.appendChild(dropdown);
        const chip = document.getElementById('llSendStatusChip');
        const rect = chip.getBoundingClientRect();
        dropdown.style.top = (rect.bottom + 4) + 'px';
        dropdown.style.left = rect.left + 'px';
        dropdown.querySelectorAll('button[data-val]').forEach(btn => {
            btn.addEventListener('click', () => {
                const newVal = btn.dataset.val;
                if (dev) dev.ll_send_status = newVal;
                renderSendStatusChip(newVal);
                updateSendBtn(newVal);
                applyChipState(item.row.querySelector('.ll-chip-container'), item.stepId, item.devIndex, 'yes');
                patchDev(item.stepId, item.devIndex, { ll_send_status: newVal });
                dropdown.remove(); if (_sendCleanup) { document.removeEventListener('click', _sendCleanup); _sendCleanup = null; }
            });
        });
        _sendCleanup = (ev) => { if (!dropdown.contains(ev.target)) { dropdown.remove(); document.removeEventListener('click', _sendCleanup); _sendCleanup = null; } };
        setTimeout(() => document.addEventListener('click', _sendCleanup), 0);
    };

    function updateSendBtn(sendStatus) {
        const btn = document.getElementById('llSendBtn');
        if (!btn) return;
        if (sendStatus === 'sent') {
            btn.className = 'flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-semibold transition-colors bg-green-100 text-green-700 cursor-default';
            btn.innerHTML = '<span class="material-icons-outlined text-base">check_circle</span> Gesendet';
            btn.onclick = null;
        } else {
            btn.className = 'flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-semibold transition-colors shadow-sm bg-amber-500 text-white hover:bg-amber-600';
            btn.innerHTML = '<span class="material-icons-outlined text-base">send</span> An DB senden';
            btn.onclick = sendLLToDb;
        }
    }

    function renderLLThread(dev) {
        const PROJECT_UUID_LL = '{{ project_uuid }}';
        const hubTypeLabels = { befund: 'Befundbeauftragung', schuettware: 'Schüttware', bauteil: 'Bauteil' };
        const hubType  = dev.hub_incident_type || '';
        const hubNr    = dev.hub_incident_number || '';
        const timelineItems = [];

        if (hubType) {
            const hubTypeLabel = hubTypeLabels[hubType] || hubType;
            const hubNrHtml = hubNr
                ? `<div class="flex items-center gap-2"><span class="material-icons-outlined text-blue-600 text-[18px]">confirmation_number</span><span class="text-[12px] text-blue-800">${hubTypeLabel} — Inc. Nr.: <strong>${hubNr}</strong></span></div>`
                : `<div class="flex items-center gap-2"><span class="material-icons-outlined text-blue-600 text-[18px]">open_in_new</span><span class="text-[12px] text-blue-800">${hubTypeLabel} — Incident eröffnet</span></div>`;
            timelineItems.push(`<div class="relative pl-5 pb-4"><div class="absolute -left-[5px] top-1 w-2 h-2 rounded-full bg-blue-500 ring-4 ring-white z-10"></div><div class="bg-blue-50 border border-blue-200 rounded-lg p-2.5">${hubNrHtml}</div></div>`);
        }

        (dev.thread || []).forEach(t => {
            const dotColor   = t.type === 'solution' ? 'bg-green-500' : t.type === 'comment' ? 'bg-slate-400' : 'bg-orange-400';
            const typeColor  = t.type === 'solution' ? 'text-green-600' : t.type === 'comment' ? 'text-slate-500' : 'text-orange-600';
            const typeLabel  = t.type === 'solution' ? 'Lösung' : t.type === 'comment' ? 'Kommentar' : 'Maßnahme';
            let refHtml = '';
            if (t.type === 'solution' && t.references) {
                const ref = (dev.thread || []).find(x => x.id === t.references);
                if (ref) refHtml = `<div class="mt-2 p-2 bg-green-100 border border-green-200 rounded text-xs text-green-800 flex items-start gap-1"><span class="material-icons-outlined text-[14px] mt-0.5">verified</span><div><span class="font-bold block">Erfolgreiche Maßnahme:</span>${ref.text}</div></div>`;
            }
            let attachHtml = '';
            if (t.attachments && t.attachments.length) {
                attachHtml = `<div class="mt-2 flex flex-wrap gap-1.5">${t.attachments.map(a =>
                    `<div class="w-16 h-16 rounded overflow-hidden border border-slate-200 cursor-pointer shadow-sm" onclick="window.open('/api/deviation/attachments/${PROJECT_UUID_LL}/${a.filename}','_blank')"><img src="/api/deviation/attachments/${PROJECT_UUID_LL}/${a.filename}" class="w-full h-full object-cover"></div>`
                ).join('')}</div>`;
            }
            timelineItems.push(`<div class="relative pl-5 pb-4"><div class="absolute -left-[5px] top-1 w-2 h-2 rounded-full ${dotColor} ring-4 ring-white z-10"></div><div class="bg-white border border-slate-200 rounded-lg p-2.5 shadow-sm"><div class="flex items-start mb-1"><div><span class="text-[10px] font-bold uppercase ${typeColor}">${typeLabel}</span><p class="text-[9px] text-slate-400 font-mono mt-0.5">${t.author || ''} &middot; ${t.date || ''}</p></div></div><p class="text-[13px] text-slate-700 leading-snug">${t.text || ''}</p>${refHtml}${attachHtml}</div></div>`);
        });

        if (!timelineItems.length) return '<p class="text-xs text-slate-400 italic">Kein Chatverlauf vorhanden.</p>';
        return `<div class="relative border-l-2 border-slate-200 ml-[3px]">${timelineItems.map((item, i) => `<div>${item}</div>`).join('')}</div>`;
    }

    function loadDrawer(idx) {
        if (!llItems.length) return;
        currentLLIndex = idx;
        const item = llItems[idx];
        const dev  = ALL_DEVS.find(d => d.step_id === item.stepId && d.dev_index === item.devIndex);
        if (!dev) return;

        document.getElementById('llDrawerDevId').textContent  = dev.dev_id || `Deviation #${item.devIndex + 1}`;
        document.getElementById('llCtxStep').textContent      = dev.step_name || '—';
        document.getElementById('llCtxReason').textContent    = dev.reason || '—';
        document.getElementById('llCtxReported').textContent  = [dev.reported_by, dev.reported_at].filter(Boolean).join(' · ') || '—';

        const detWrap = document.getElementById('llCtxDetailsWrap');
        const detEl   = document.getElementById('llCtxDetails');
        if (dev.details) { detEl.textContent = dev.details; detWrap.classList.remove('hidden'); }
        else { detWrap.classList.add('hidden'); }

        document.getElementById('llCtxThread').innerHTML = renderLLThread(dev);

        document.getElementById('llFieldNote').value           = dev.ll_note           || '';
        document.getElementById('llFieldCause').value          = dev.ll_cause          || '';
        document.getElementById('llFieldMeasure').value        = dev.ll_measure        || '';
        document.getElementById('llFieldRecommendation').value = dev.ll_recommendation || '';
        document.getElementById('llFieldCategory').value       = dev.ll_category       || '';

        const sendStatus = dev.ll_send_status || '';
        renderSendStatusChip(sendStatus);
        updateSendBtn(sendStatus);

        document.getElementById('llDrawerCounter').textContent = `${idx + 1} / ${llItems.length}`;
        document.getElementById('llDrawerPrev').disabled = idx === 0;
        document.getElementById('llDrawerNext').disabled = idx === llItems.length - 1;
        document.getElementById('llDrawerSaveStatus').textContent = '';
    }

    window.openLLDrawer = function(stepId, devIndex) {
        buildLLItems();
        const idx = llItems.findIndex(i => i.stepId === stepId && i.devIndex === devIndex);
        loadDrawer(idx >= 0 ? idx : 0);
        document.getElementById('llDrawerOverlay').classList.remove('hidden');
        document.getElementById('llDrawer').classList.remove('translate-x-full');
    };

    window.closeLLDrawer = function() {
        document.getElementById('llDrawer').classList.add('translate-x-full');
        document.getElementById('llDrawerOverlay').classList.add('hidden');
    };

    window.navigateLLDrawer = function(dir) {
        const next = currentLLIndex + dir;
        if (next >= 0 && next < llItems.length) loadDrawer(next);
    };

    window.saveLLDrawer = async function() {
        const item = llItems[currentLLIndex];
        if (!item) return;
        const patch = {
            ll:                 true,
            ll_note:            document.getElementById('llFieldNote').value,
            ll_cause:           document.getElementById('llFieldCause').value,
            ll_measure:         document.getElementById('llFieldMeasure').value,
            ll_recommendation:  document.getElementById('llFieldRecommendation').value,
            ll_category:        document.getElementById('llFieldCategory').value,
        };
        const dev = ALL_DEVS.find(d => d.step_id === item.stepId && d.dev_index === item.devIndex);
        // Auto-detect send status from content (never downgrade from 'sent')
        if (dev && dev.ll_send_status !== 'sent') {
            const hasContent = !!(patch.ll_note || patch.ll_cause || patch.ll_measure || patch.ll_recommendation || patch.ll_category);
            const autoStatus = hasContent ? 'in_progress' : '';
            if (autoStatus !== (dev.ll_send_status || '')) {
                patch.ll_send_status = autoStatus;
                dev.ll_send_status = autoStatus;
                renderSendStatusChip(autoStatus);
                updateSendBtn(autoStatus);
                applyChipState(item.row.querySelector('.ll-chip-container'), item.stepId, item.devIndex, 'yes');
            }
        }
        if (dev) { Object.assign(dev, patch); }
        try {
            await fetch(PATCH_URL, {
                method: 'PATCH',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({ step_id_str: item.stepId, dev_index: item.devIndex, patch })
            });
            const status = document.getElementById('llDrawerSaveStatus');
            status.textContent = 'Gespeichert ✓';
            status.className = 'text-xs text-green-600 font-medium';
            setTimeout(() => { status.textContent = ''; }, 2500);
        } catch(e) {
            document.getElementById('llDrawerSaveStatus').textContent = 'Fehler beim Speichern';
        }
    };

    window.sendLLToDb = async function() {
        const item = llItems[currentLLIndex];
        if (!item) return;
        await saveLLDrawer();
        const dev = ALL_DEVS.find(d => d.step_id === item.stepId && d.dev_index === item.devIndex);
        if (dev) dev.ll_send_status = 'sent';
        renderSendStatusChip('sent');
        updateSendBtn('sent');
        applyChipState(item.row.querySelector('.ll-chip-container'), item.stepId, item.devIndex, 'yes');
        await patchDev(item.stepId, item.devIndex, { ll_send_status: 'sent' });
        const status = document.getElementById('llDrawerSaveStatus');
        status.textContent = 'An Datenbank gesendet ✓';
        status.className = 'text-xs text-green-600 font-medium';
        setTimeout(() => { status.textContent = ''; }, 3000);
    };

    document.addEventListener('keydown', e => {
        if (document.getElementById('llDrawer').classList.contains('translate-x-full')) return;
        if (e.key === 'Escape') closeLLDrawer();
        if (e.key === 'ArrowLeft'  && !e.target.matches('input,textarea,select')) navigateLLDrawer(-1);
        if (e.key === 'ArrowRight' && !e.target.matches('input,textarea,select')) navigateLLDrawer(1);
    });
})();
</script>

<!-- Slave Units Upload Modal -->
<div id="slaveUnitsModal" class="hidden fixed inset-0 z-[999] flex items-center justify-center">
    <div class="absolute inset-0 bg-black/40 backdrop-blur-sm" onclick="closeSlaveUnitsModal()"></div>
    <div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg mx-4 flex flex-col overflow-hidden">
        <!-- Header -->
        <div class="flex items-center justify-between px-6 py-4 border-b border-slate-200">
            <div>
                <p class="text-lg font-bold text-slate-800">Slave Units hochladen</p>
                <p class="text-xs text-slate-500 mt-0.5">CSV mit NAME, PN und SN Spalten</p>
            </div>
            <button onclick="closeSlaveUnitsModal()" class="p-2 rounded-full hover:bg-slate-100 text-slate-400 hover:text-slate-600">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>
        <!-- Body -->
        <div class="px-6 py-5 space-y-5">
            <!-- Single entry form -->
            <div>
                <p class="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Einzelne Slave Unit hinzufügen</p>
                <div class="grid grid-cols-3 gap-2 mb-2">
                    <div>
                        <label class="block text-[11px] text-slate-500 mb-1">NAME</label>
                        <input id="suInputName" type="text" placeholder="z.B. Gear Box Assembly" class="w-full text-sm border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400">
                    </div>
                    <div>
                        <label class="block text-[11px] text-slate-500 mb-1">PN</label>
                        <input id="suInputPn" type="text" placeholder="z.B. GBA-4711" class="w-full text-sm border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400">
                    </div>
                    <div>
                        <label class="block text-[11px] text-slate-500 mb-1">SN</label>
                        <input id="suInputSn" type="text" placeholder="z.B. SN-00123" class="w-full text-sm border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-amber-400">
                    </div>
                </div>
                <div class="flex items-center justify-between">
                    <p id="suAddError" class="text-xs text-red-500 hidden"></p>
                    <button onclick="addSlaveUnit()" class="ml-auto flex items-center gap-1.5 px-4 py-2 text-sm font-semibold text-white bg-amber-600 hover:bg-amber-700 rounded-lg transition-colors">
                        <span class="material-icons-outlined text-base">add</span> Hinzufügen
                    </button>
                </div>
            </div>

            <div class="flex items-center gap-3">
                <div class="flex-1 h-px bg-slate-200"></div>
                <span class="text-xs text-slate-400 font-medium">oder CSV importieren</span>
                <div class="flex-1 h-px bg-slate-200"></div>
            </div>

            <!-- Format info -->
            <div class="bg-slate-50 rounded-lg border border-slate-200 overflow-hidden">
                <div class="px-4 py-2 border-b border-slate-200 flex items-center gap-2">
                    <span class="material-icons-outlined text-slate-400 text-base">table_view</span>
                    <p class="text-xs font-semibold text-slate-600 uppercase tracking-wide">Erwartetes CSV-Format</p>
                </div>
                <div class="overflow-x-auto">
                    <table class="w-full text-xs">
                        <thead class="bg-slate-100 text-slate-500 font-semibold">
                            <tr>
                                <th class="px-4 py-2 text-left">NAME</th>
                                <th class="px-4 py-2 text-left">PN</th>
                                <th class="px-4 py-2 text-left">SN</th>
                            </tr>
                        </thead>
                        <tbody class="text-slate-700">
                            <tr class="border-t border-slate-200">
                                <td class="px-4 py-2 font-medium">Gear Box Assembly</td>
                                <td class="px-4 py-2 font-mono">GBA-4711</td>
                                <td class="px-4 py-2 font-mono">SN-00123</td>
                            </tr>
                            <tr class="border-t border-slate-100 bg-slate-50/50">
                                <td class="px-4 py-2 font-medium">Turbine Blade Set</td>
                                <td class="px-4 py-2 font-mono">TBS-0815</td>
                                <td class="px-4 py-2 font-mono">SN-00456</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
                <p class="px-4 py-2 text-[11px] text-slate-400 border-t border-slate-200">Spaltenköpfe müssen NAME, PN und SN heißen (Groß-/Kleinschreibung egal). Weitere Spalten werden ignoriert.</p>
            </div>
            <!-- Drop zone -->
            <label id="slaveUnitsDropZone" class="flex flex-col items-center justify-center w-full h-28 border-2 border-dashed border-slate-300 rounded-xl cursor-pointer hover:border-amber-400 hover:bg-amber-50 transition-colors">
                <span class="material-icons-outlined text-3xl text-slate-300">upload_file</span>
                <span class="text-sm text-slate-500 mt-1">CSV-Datei auswählen oder hier ablegen</span>
                <span id="slaveUnitsFileName" class="text-xs text-amber-600 font-medium mt-1 hidden"></span>
                <input type="file" accept=".csv" class="hidden" id="slaveUnitsCsvInput" onchange="uploadSlaveUnits(this)">
            </label>
        </div>
        <!-- Footer -->
        <div class="px-6 py-4 border-t border-slate-200 flex items-center justify-between gap-3">
            <button id="slaveUnitsDeleteAllBtn" onclick="deleteSlaveUnits()" class="hidden flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-red-600 bg-white border border-red-200 rounded-lg hover:bg-red-50 transition-colors">
                <span class="material-icons-outlined text-base">delete_sweep</span> Alle löschen
            </button>
            <div class="flex items-center gap-3 ml-auto">
                <button onclick="closeSlaveUnitsModal()" class="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50">Abbrechen</button>
                <button onclick="document.getElementById('slaveUnitsCsvInput').click()" class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-white bg-amber-600 hover:bg-amber-700 rounded-lg transition-colors">
                    <span class="material-icons-outlined text-base">upload_file</span> CSV hochladen
                </button>
            </div>
        </div>
    </div>
</div>

</body>
</html>
"""

PROJECT_PROTOCOL_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Protocol – {{ project.get('name', project.get('project_code','Project')) }}</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #1a2035; background: #e8edf2; }

  @media screen {
    .print-bar {
      position: fixed; top: 0; left: 0; right: 0; z-index: 100;
      background: #1e3a5f; color: white;
      padding: 0.55rem 1.5rem; display: flex; align-items: center; gap: 1rem;
      box-shadow: 0 2px 8px rgba(0,0,0,0.2);
    }
    .print-bar button {
      background: white; color: #1e3a5f; border: none; cursor: pointer;
      padding: 0.3rem 1rem; border-radius: 4px; font-weight: bold; font-size: 11px;
    }
    .print-bar a { color: rgba(255,255,255,0.75); text-decoration: none; font-size: 11px; }
    .print-bar a:hover { color: white; }
    .print-bar .spacer { margin-left: auto; opacity: 0.6; font-size: 10px; }
    .page {
      background: white; max-width: 820px; margin: 4rem auto 3rem auto;
      padding: 2.5rem 2.5rem 3rem 2.5rem;
      box-shadow: 0 4px 24px rgba(0,0,0,0.12);
    }
  }

  @media print {
    body { background: white; }
    .print-bar { display: none !important; }
    .page { margin: 0; padding: 1.5cm 2cm; box-shadow: none; max-width: none; }
    table { page-break-inside: avoid; }
    .step-top { page-break-inside: avoid; }
  }

  /* ── Document header ── */
  .doc-header {
    display: flex; justify-content: space-between; align-items: flex-start;
    padding-bottom: 1rem; border-bottom: 3px solid #1e3a5f; margin-bottom: 1.5rem;
  }
  .doc-title { font-size: 20px; font-weight: 900; color: #1e3a5f; letter-spacing: 0.04em; }
  .doc-subtitle { font-size: 11px; color: #64748b; margin-top: 0.2rem; }
  .doc-meta { text-align: right; }
  .meta-grid { display: grid; grid-template-columns: auto auto; gap: 0.1rem 1rem; margin-top: 0.5rem; }
  .meta-label { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #94a3b8; text-align: right; }
  .meta-value { font-size: 10.5px; font-weight: 600; color: #1e293b; }

  /* ── Progress bar ── */

  /* ── Step rows ── */
  .step-top {
    background: #1e3a5f; color: white;
    padding: 0.45rem 0.75rem; margin-top: 1.1rem;
    display: flex; justify-content: space-between; align-items: center;
    font-weight: 700; font-size: 11px; border-radius: 3px 3px 0 0;
  }
  .step-sub {
    background: #dbeafe; color: #1e40af;
    padding: 0.35rem 0.75rem 0.35rem 1.5rem;
    display: flex; justify-content: space-between; align-items: center;
    font-weight: 600; font-size: 10.5px;
    border-left: 3px solid #3b82f6; border-bottom: 1px solid #bfdbfe;
  }
  .step-signoff { font-size: 9.5px; font-weight: 400; opacity: 0.85; white-space: nowrap; margin-left: 1rem; flex-shrink: 0; }
  .step-top .step-signoff { color: rgba(255,255,255,0.85); }
  .step-sub .step-signoff { color: #1e40af; }

  /* ── Instruction table ── */
  table.inst { width: 100%; border-collapse: collapse; font-size: 10px; }
  table.inst th {
    background: #f8fafc; text-align: left; padding: 0.3rem 0.5rem;
    font-size: 8.5px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: #64748b;
    border-bottom: 1px solid #e2e8f0; border-top: 1px solid #e2e8f0;
  }
  table.inst td {
    padding: 0.3rem 0.5rem; border-bottom: 1px solid #f1f5f9;
    vertical-align: top; line-height: 1.4;
  }
  table.inst tr:last-child td { border-bottom: none; }
  table.inst tr:hover td { background: #fafafa; }
  .col-nr     { width: 5%;  }
  .col-text   { width: 32%; }
  .col-detail { width: 24%; color: #334155; font-style: italic; }
  .col-st     { width: 7%;  }
  .col-by     { width: 17%; }
  .col-at     { width: 15%; }
  .chk-ok    { color: #166534; font-weight: 700; }
  .chk-na    { color: #92400e; font-weight: 700; }
  .chk-open  { color: #cbd5e1; }

  /* ── Cycle badge ── */
  .cycle-header {
    background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 3px;
    padding: 0.3rem 0.75rem; margin-top: 1.5rem; margin-bottom: 0.25rem;
    font-size: 10px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.07em; color: #475569;
  }

  /* ── Deviations summary ── */
  .dev-section { margin-top: 2rem; border-top: 1px solid #e2e8f0; padding-top: 1rem; }
  .dev-title { font-size: 12px; font-weight: 700; color: #1e3a5f; margin-bottom: 0.5rem; }
  table.dev { width: 100%; border-collapse: collapse; font-size: 10px; }
  table.dev th {
    background: #f8fafc; text-align: left; padding: 0.3rem 0.5rem;
    font-size: 8.5px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: #64748b; border-bottom: 1px solid #e2e8f0;
  }
  table.dev td { padding: 0.35rem 0.5rem; border-bottom: 1px solid #f1f5f9; vertical-align: top; }
  .dev-open   { color: #991b1b; font-weight: 700; }
  .dev-closed { color: #166534; font-weight: 700; }

  /* ── Signature block ── */
  .sig-section { margin-top: 3rem; }
  .sig-row { display: flex; gap: 2.5rem; }
  .sig-box { flex: 1; }
  .sig-line { border-top: 1px solid #334155; margin-top: 2.5rem; padding-top: 0.3rem; font-size: 9px; color: #64748b; }

  /* ── Footer ── */
  .doc-footer {
    margin-top: 2rem; padding-top: 0.75rem; border-top: 1px solid #e8ecf0;
    display: flex; justify-content: space-between; font-size: 9px; color: #94a3b8;
  }
</style>
</head>
<body>

<div class="print-bar">
  <button onclick="window.print()">&#128438; Print / PDF</button>
  <a href="{{ url_for('serve_engineer_report', project_uuid=project_uuid) }}">&#8592; Back to Report</a>
  <span class="spacer">Test Protocol &middot; {{ project.get('name', project.get('project_code','')) }} &middot; SN {{ project.get('engine_serial','—') }}</span>
</div>

<div class="page">

  <!-- Header -->
  <div class="doc-header">
    <div style="display:flex;align-items:center;gap:1.25rem;">
      <img src="{{ url_for('static', filename='mtu-logo.png') }}" alt="MTU Logo" style="height:48px;object-fit:contain;">
      <div>
        <div class="doc-title">TEST PROTOCOL</div>
        <div class="doc-subtitle">Step &amp; Instruction Sign-off Record</div>
      </div>
    </div>
    <div class="doc-meta">
      <div class="meta-grid">
        <span class="meta-label">Project</span>
        <span class="meta-value">{{ project.get('name', project.get('project_code','—')) }}</span>
        <span class="meta-label">Engine S/N</span>
        <span class="meta-value">{{ project.get('engine_serial', '—') }}</span>
        <span class="meta-label">Engine Type</span>
        <span class="meta-value">{{ project.get('engine_type', '—') }}</span>
        <span class="meta-label">Test Cell</span>
        <span class="meta-value">{{ project.get('test_cell', '—') }}</span>
        <span class="meta-label">Status</span>
        <span class="meta-value">{{ project.get('status', '—') }}</span>
        <span class="meta-label">Created</span>
        <span class="meta-value">{{ report_date }}</span>
      </div>
    </div>
  </div>

  <!-- Steps -->
  {% set ns = namespace(last_cycle=None) %}
  {% for step in protocol_steps %}
    {% if step.cycle_number and step.cycle_number != ns.last_cycle %}
      {% set ns.last_cycle = step.cycle_number %}
      <div class="cycle-header">Cycle {{ step.cycle_number }}</div>
    {% endif %}

    <div class="step-top">
      <span>{{ step.id_str }}&nbsp;&nbsp;{{ step.name }}</span>
      <span class="step-signoff">
        {% if step.status == 'Completed' %}&#10003; Completed
        {% elif step.status == 'N/A' %}N/A
        {% elif step.status == 'In Progress' %}&#8987; In Progress
        {% elif step.status == 'On Hold' %}&#9646; On Hold
        {% else %}{{ step.status or '—' }}{% endif %}
        {% if step.completed_by %}&nbsp;&middot;&nbsp;{{ step.completed_by }}{% if step.completed_at %}&nbsp;({{ step.completed_at }}){% endif %}{% endif %}
      </span>
    </div>

    {% if step.instructions %}
    <table class="inst">
      <thead><tr>
        <th class="col-nr">No.</th>
        <th class="col-text">Instruction</th>
        <th class="col-detail">Selection / Input</th>
        <th class="col-st">Status</th>
        <th class="col-by">Signed off by</th>
        <th class="col-at">Date / Time</th>
      </tr></thead>
      <tbody>
      {% for inst in step.instructions %}
      <tr>
        <td>{{ inst.step_num or '—' }}</td>
        <td>{{ inst.text_clean }}</td>
        <td class="col-detail">{{ inst.detail or '' }}</td>
        <td>
          {% if inst.checked %}<span class="chk-ok">&#10003;</span>
          {% elif inst.nr %}<span class="chk-na">N/A</span>
          {% else %}<span class="chk-open">—</span>{% endif %}
        </td>
        <td>{{ inst.completed_by or '—' }}</td>
        <td>{{ inst.completed_at or '—' }}</td>
      </tr>
      {% endfor %}
      </tbody>
    </table>
    {% endif %}

    {% for child in step.children %}
    <div class="step-sub">
      <span>{{ child.id_str }}&nbsp;&nbsp;{{ child.name }}</span>
      <span class="step-signoff">
        {% if child.status == 'Completed' %}&#10003; Completed
        {% elif child.status == 'N/A' %}N/A
        {% elif child.status == 'In Progress' %}&#8987; In Progress
        {% elif child.status == 'On Hold' %}&#9646; On Hold
        {% else %}{{ child.status or '—' }}{% endif %}
        {% if child.completed_by %}&nbsp;&middot;&nbsp;{{ child.completed_by }}{% if child.completed_at %}&nbsp;({{ child.completed_at }}){% endif %}{% endif %}
      </span>
    </div>
    {% if child.instructions %}
    <table class="inst">
      <thead><tr>
        <th class="col-nr">No.</th>
        <th class="col-text">Instruction</th>
        <th class="col-detail">Selection / Input</th>
        <th class="col-st">Status</th>
        <th class="col-by">Signed off by</th>
        <th class="col-at">Date / Time</th>
      </tr></thead>
      <tbody>
      {% for inst in child.instructions %}
      <tr>
        <td>{{ inst.step_num or '—' }}</td>
        <td>{{ inst.text_clean }}</td>
        <td class="col-detail">{{ inst.detail or '' }}</td>
        <td>
          {% if inst.checked %}<span class="chk-ok">&#10003;</span>
          {% elif inst.nr %}<span class="chk-na">N/A</span>
          {% else %}<span class="chk-open">—</span>{% endif %}
        </td>
        <td>{{ inst.completed_by or '—' }}</td>
        <td>{{ inst.completed_at or '—' }}</td>
      </tr>
      {% endfor %}
      </tbody>
    </table>
    {% endif %}
    {% endfor %}

  {% endfor %}

  <!-- Deviations summary -->
  {% if deviations %}
  <div class="dev-section">
    <div class="dev-title">Deviations ({{ deviations|length }})</div>
    <table class="dev">
      <thead><tr>
        <th style="width:12%">Step</th>
        <th style="width:35%">Reason</th>
        <th style="width:10%">Status</th>
        <th style="width:18%">Reported by</th>
        <th style="width:15%">Reported at</th>
        <th style="width:10%">HUB No.</th>
      </tr></thead>
      <tbody>
      {% for dev in deviations %}
      <tr>
        <td>{{ dev.step_name }}</td>
        <td>{{ dev.reason }}</td>
        <td>
          {% if dev.status == 'Open' %}<span class="dev-open">Open</span>
          {% else %}<span class="dev-closed">Closed</span>{% endif %}
        </td>
        <td>{{ dev.reported_by or '—' }}</td>
        <td>{{ dev.reported_at or '—' }}</td>
        <td>{{ dev.hub_incident_number or '—' }}</td>
      </tr>
      {% endfor %}
      </tbody>
    </table>
  </div>
  {% endif %}

  <!-- Signature block -->
  <div class="sig-section">
    <div class="sig-row">
      <div class="sig-box"><div class="sig-line">Test Manager &nbsp; / &nbsp; Date</div></div>
      <div class="sig-box"><div class="sig-line">Technician &nbsp; / &nbsp; Date</div></div>
      <div class="sig-box"><div class="sig-line">QM &nbsp; / &nbsp; Date</div></div>
    </div>
  </div>

  <!-- Footer -->
  <div class="doc-footer">
    <span>{{ project.get('name', '') }} &nbsp;|&nbsp; S/N: {{ project.get('engine_serial','—') }} &nbsp;|&nbsp; {{ project.get('engine_type','') }}</span>
    <span>Protocol date: {{ report_date }}</span>
  </div>

</div>
</body>
</html>
"""

TEST_EQUIPMENT_PAGE_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
    <title>MBET - Test Hardware Management</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        body { font-family: 'Inter', sans-serif; background-color: #f8fafc; }

        /* Sidebar Styles */
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }

        /* Styles für Test Equipment */
        .card {
            background-color: white;
            border-radius: 0.5rem;
            box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            border: 1px solid #e2e8f0;
            padding: 1.5rem;
            display: flex; flex-direction: column;
        }

        /* Runde Status-Indikatoren */
        .status-indicator {
            width: 1.5rem; height: 1.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 700; color: white;
            display: flex; align-items: center; justify-content: center; margin-right: 0.25rem; box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        }
        .bg-green-500 { background-color: #10B981 !important; }
        .bg-blue-500 { background-color: #3B82F6 !important; }
        .bg-yellow-500 { background-color: #F59E0B !important; }
        .bg-red-500 { background-color: #EF4444 !important; }

        .status-badge { padding: 0.125rem 0.625rem; font-size: 0.75rem; font-weight: 500; border-radius: 9999px; display: inline-flex; align-items: center; }
        .badge-available { background-color: #dcfce7; color: #166534; }
        .badge-maintenance { background-color: #fef9c3; color: #854d0e; }
        .badge-decommissioned { background-color: #fee2e2; color: #991b1b; }
        .badge-in-order { background-color: #f1f5f9; color: #475569; }

        /* Kit & Trolley Tags */
        .kit-tag, .trolley-tag {
            display: inline-flex; align-items: center; background-color: #f1f5f9; border: 1px solid #cbd5e1;
            color: #334155; font-size: 0.75rem; font-weight: 600; margin-right: 0.5rem; margin-bottom: 0.5rem;
            padding: 0.25rem 0.75rem; border-radius: 9999px; cursor: pointer; transition: background-color 0.2s;
        }
        .kit-tag:hover, .trolley-tag:hover { background-color: #e2e8f0; }

        .kit-status-dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; margin-right: 0.5rem; box-shadow: 0 0 0 1px white; }
        .bg-kit-ready { background-color: #10B981; }
        .bg-kit-partial { background-color: #F59E0B; }
        .bg-kit-missing { background-color: #EF4444; }

        .modal { transition: opacity 0.25s ease; z-index: 50; }
        body.modal-active { overflow-y: hidden; }
        .modal-backdrop { position: fixed; inset: 0; background-color: rgba(17, 24, 39, 0.5); z-index: 40; }
        .modal-content { position: relative; margin: 2.5rem auto; padding: 0; border: 1px solid #e5e7eb; width: 91.666667%; max-width: 56rem; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); border-radius: 0.75rem; background-color: white; z-index: 50; display: flex; flex-direction: column; max-height: 90vh; }

        .custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
        .custom-scrollbar::-webkit-scrollbar-track { background: #f1f5f9; }
        .custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
        .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #94a3b8; }

        .toggle-sn-rows .material-icons-outlined { transition: transform 0.2s ease-in-out; }
        .toggle-sn-rows.is-open .material-icons-outlined { transform: rotate(90deg); }
        .pn-row:hover td { background-color: #f8fafc; }
        .sn-row { display: none; }
        .sn-row.is-visible { display: table-row; }
        .sn-row td { background-color: #f8fafc; }
        .new-kit-tag { display: flex; align-items: center; background-color: #f3f4f6; color: #374151; font-size: 0.75rem; font-weight: 500; margin-right: 0.5rem; margin-bottom: 0.5rem; padding-left: 0.75rem; padding-right: 0.5rem; padding-top: 0.25rem; padding-bottom: 0.25rem; border-radius: 9999px; border: 1px solid #e5e7eb; }

        .btn-icon-only { @apply text-blue-600 hover:text-blue-800 p-1 rounded-full hover:bg-blue-50 transition-colors; }
        .expand-all-btn { @apply p-1 rounded hover:bg-slate-200 text-slate-500 transition-colors cursor-pointer mr-2; }

        .product-type-tag { 
            display: inline-block;
            background-color: #f1f5f9; /* slate-100 */
            color: #475569;            /* slate-600 */
            font-size: 0.75rem;        /* text-xs */
            font-weight: 500;          /* font-medium */
            padding: 0.125rem 0.5rem;  /* px-2 py-0.5 */
            border-radius: 0.25rem;    /* rounded */
            border: 1px solid #e2e8f0; /* border-slate-200 */
            margin-right: 0.25rem;
            margin-bottom: 0.25rem;
        }

        /* Tag Input im Modal (Pillen-Design) */
        .tag-input-wrapper { 
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            border: 1px solid #cbd5e1; /* slate-300 */
            border-radius: 0.5rem;     /* rounded-lg */
            padding: 0.375rem;         /* p-1.5 */
            background-color: white;
        }
        .tag-input-wrapper:focus-within {
            ring: 2px solid #3b82f6;   /* focus ring simulation */
            border-color: #3b82f6;
        }

        .tag-chip { 
            background-color: #dbeafe; /* blue-100 */
            color: #1e40af;            /* blue-800 */
            font-size: 0.75rem;        /* text-xs */
            font-weight: 600;          /* font-semibold */
            padding: 0.125rem 0.5rem;  /* px-2 py-0.5 */
            border-radius: 0.375rem;   /* rounded-md */
            display: flex;
            align-items: center;
            margin-right: 0.25rem;
            margin-bottom: 0.25rem;
        }

        .tag-chip-remove { 
            margin-left: 0.25rem;
            color: #3b82f6;            /* blue-500 */
            cursor: pointer;
            font-weight: bold;
        }
        .tag-chip-remove:hover { color: #1d4ed8; /* blue-700 */ }

        .tag-input-field { 
            flex-grow: 1;
            min-width: 80px;
            border: none;
            outline: none;
            font-size: 0.875rem;       /* text-sm */
            padding: 0.25rem;
        }
        .tag-input-field:focus { box-shadow: none; }

        .tag-dropdown { 
            position: absolute;
            z-index: 50;
            width: 100%;
            background-color: white;
            border: 1px solid #cbd5e1;
            border-radius: 0.5rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
            margin-top: 0.25rem;
            max-height: 10rem;
            overflow-y: auto;
            display: none; /* Standardmäßig versteckt */
        }
        .tag-dropdown.hidden { display: none; } /* Sicherstellen, dass hidden greift */
        .tag-dropdown:not(.hidden) { display: block; }

        .tag-dropdown-item { 
            padding: 0.5rem 0.75rem;
            font-size: 0.875rem;
            color: #334155;
            cursor: pointer;
        }
        .tag-dropdown-item:hover { background-color: #f8fafc; }

        /* Styles für Maintenance Popover */
        .maint-popover-wrapper { position: relative; display: inline-block; }
        .maint-popover-content {
            visibility: hidden; opacity: 0; position: fixed;
            width: 280px; background-color: white; border: 1px solid #e2e8f0; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
            border-radius: 0.5rem; padding: 0.5rem; z-index: 9999; transition: opacity 0.2s, visibility 0.2s;
            text-align: left; pointer-events: auto;
        }
        .maint-popover-content.is-visible { visibility: visible; opacity: 1; }
        .maint-popover-content::after {
            content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px;
            border-width: 5px; border-style: solid; border-color: white transparent transparent transparent;
        }
    </style>
</head>
<body class="text-slate-800">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200 sticky top-0 z-30">
             <div class="flex items-center gap-3">
                <h1 class="text-2xl font-bold tracking-tight text-slate-800">Hardware & Test Equipment</h1>
             </div>
             <div class="flex items-center gap-4">
             </div>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">

<!-- Middle Section: Mapping, Rüstwagen & Lager -->
            <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-24">
                
                <!-- 1. Engine to Kit Mapping Table -->
                <div class="card h-full">
                    <div class="flex justify-between items-start mb-4">
                        <div>
                            <h2 class="text-lg font-bold text-slate-800">Engine Mapping</h2>
                            <p class="text-sm text-slate-500">Zuordnung von Hardware-Kits</p>
                        </div>
                        {% if 'TESTMECHANIC' not in session.get('roles', []) %}
                        <button class="btn-icon-only" onclick="openNewMappingModal()" title="Neues Mapping">
                            <span class="material-icons-outlined text-2xl">add_circle</span>
                        </button>
                        {% endif %}
                    </div>
                    <div class="overflow-x-auto border border-slate-200 rounded-lg flex-grow max-h-64 custom-scrollbar">
                        <table class="w-full min-w-[300px] text-sm text-left" id="engineKitMappingTable">
                            <thead class="bg-slate-50 text-slate-600 uppercase text-xs font-semibold sticky top-0 z-10">
                                <tr>
                                    <th class="px-4 py-3 border-b bg-slate-50 cursor-pointer select-none" onclick="cycleSortDir('mapping',0);renderMappingTable()">Engine <span id="sort-mapping-0"></span></th>
                                    <th class="px-4 py-3 border-b bg-slate-50">Kits</th>
                                    <th class="px-4 py-3 border-b text-right bg-slate-50"></th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-slate-200 bg-white"></tbody>
                        </table>
                    </div>
                </div>

                <!-- 2. Rüstwagen (Trolleys) -->
                <div class="card h-full">
                    <div class="flex justify-between items-start mb-4">
                        <div>
                            <h2 class="text-lg font-bold text-slate-800">Rüstwagen</h2>
                            <p class="text-sm text-slate-500">Mobile Lagereinheiten</p>
                        </div>
                        <!-- WICHTIG: Hier rufen wir jetzt openStandardTrolleyModal auf -->
                        {% if 'TESTMECHANIC' not in session.get('roles', []) %}
                        <button class="btn-icon-only" onclick="openStandardTrolleyModal(null)" title="Neuer Rüstwagen">
                            <span class="material-icons-outlined text-2xl">add_circle</span>
                        </button>
                        {% endif %}
                    </div>
                    <div class="overflow-x-auto border border-slate-200 rounded-lg flex-grow max-h-64 custom-scrollbar">
                        <table class="w-full min-w-[300px] text-sm text-left" id="trolleyTable">
                            <thead class="bg-slate-50 text-slate-600 uppercase text-xs font-semibold sticky top-0 z-10">
                                <tr>
                                    <th class="px-4 py-3 border-b bg-slate-50 cursor-pointer select-none" onclick="cycleSortDir('trolley',0);renderTrolleyTable()">Engine <span id="sort-trolley-0"></span></th>
                                    <th class="px-4 py-3 border-b bg-slate-50 cursor-pointer select-none" onclick="cycleSortDir('trolley',1);renderTrolleyTable()">Name <span id="sort-trolley-1"></span></th>
                                    <th class="px-4 py-3 border-b text-right bg-slate-50"></th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-slate-200 bg-white" id="trolleyTableBody"></tbody>
                        </table>
                    </div>
                </div>

                <!-- 3. NEU: Lagerorte (Storage) -->
                <div class="card h-full">
                    <div class="flex justify-between items-start mb-4">
                        <div>
                            <h2 class="text-lg font-bold text-slate-800">Lagerorte</h2>
                            <p class="text-sm text-slate-500">Regale, Schränke, Kisten</p>
                        </div>
                        {% if 'TESTMECHANIC' not in session.get('roles', []) %}
                        <button class="btn-icon-only" onclick="openStorageModal(null)" title="Neuer Lagerort">
                            <span class="material-icons-outlined text-2xl">add_circle</span>
                        </button>
                        {% endif %}
                    </div>
                    <div class="overflow-x-auto border border-slate-200 rounded-lg flex-grow max-h-64 custom-scrollbar">
                        <table class="w-full min-w-[300px] text-sm text-left" id="storageTable">
                            <thead class="bg-slate-50 text-slate-600 uppercase text-xs font-semibold sticky top-0 z-10">
                                <tr>
                                    <th class="px-4 py-3 border-b bg-slate-50 cursor-pointer select-none" onclick="cycleSortDir('storage',0);renderStorageTable()">Bereich <span id="sort-storage-0"></span></th>
                                    <th class="px-4 py-3 border-b bg-slate-50 cursor-pointer select-none" onclick="cycleSortDir('storage',1);renderStorageTable()">Name <span id="sort-storage-1"></span></th>
                                    <th class="px-4 py-3 border-b text-right bg-slate-50"></th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-slate-200 bg-white" id="storageTableBody"></tbody>
                        </table>
                    </div>
                </div>
            </div>

            <!-- KPIs -->
            <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
                <div class="card flex-row items-center border-white space-x-4 hover:shadow-md transition-shadow">
                    <div class="p-3 bg-blue-50 rounded-full text-blue-600"><span class="material-icons-outlined text-3xl">memory</span></div>
                    <div><p class="text-sm text-slate-500">Total Part Numbers</p><p class="text-2xl font-bold text-slate-800" id="totalPnCount">0</p></div>
                </div>
                <div class="card flex-row items-center border-white space-x-4 hover:shadow-md transition-shadow">
                    <div class="p-3 bg-green-50 rounded-full text-green-600"><span class="material-icons-outlined text-3xl">inventory_2</span></div>
                    <div><p class="text-sm text-slate-500">Total Serial Numbers</p><p class="text-2xl font-bold text-slate-800" id="totalSnCount">0</p></div>
                </div>
                <div class="card flex-row items-center space-x-4 hover:shadow-md transition-shadow">
                    <div class="p-3 bg-amber-50 rounded-full text-amber-600"><span class="material-icons-outlined text-3xl">build_circle</span></div>
                    <div><p class="text-sm text-slate-500">In Maintenance</p><p class="text-2xl font-bold text-slate-800" id="maintenanceCount">0</p></div>
                </div>
                <div id="weeklyCheckTile" class="card flex-row items-center space-x-4 hover:shadow-md transition-shadow cursor-pointer select-none" onclick="openCompletenessProtocolModal()">
                    <div class="p-3 bg-purple-50 rounded-full text-purple-600"><span class="material-icons-outlined text-3xl">fact_check</span></div>
                    <div>
                        <p class="text-sm text-slate-500">Prüfprotokoll KW</p>
                        <p class="text-2xl font-bold text-slate-800" id="weeklyCheckCount">–</p>
                        <p class="text-xs text-slate-400">Prüfungen diese Woche</p>
                    </div>
                </div>
            </div>

            <!-- Inventory Table -->
            <div class="card">
                <div class="flex flex-col gap-4 mb-4">
                    <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
                        <div>
                            <h2 class="text-lg font-bold text-slate-800 flex items-center gap-2">
                                Hardware Inventory
                                <!-- NEU: Minimalistischer Counter -->
                                <span id="hwTableCount" class="text-xs font-normal text-slate-400 bg-slate-100 px-2 py-0.5 rounded-full hidden">0 Ergebnisse</span>
                            </h2>
                            <p class="text-sm text-slate-500">Verwaltung einzelner Hardware-Komponenten und Seriennummern</p>
                        </div>
                        
                        <div class="flex items-center gap-3 flex-wrap">
                            <!-- Search -->
                            <div class="relative">
                                <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">search</span>
                                <input type="text" id="hardwareSearchInput" oninput="applyHardwareFilters()" placeholder="Suchen..." class="pl-9 pr-3 py-1.5 rounded-full border border-slate-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm w-40 transition-all focus:w-64">
                            </div>
                            <!-- Filter Toggle Button -->
                            <div class="relative flex-shrink-0">
                                <button onclick="toggleMaintenanceFilter('filterPanelHw', 'filterBadgeHw')" title="Filter" class="w-8 h-8 flex items-center justify-center rounded-full border border-slate-300 bg-white hover:bg-slate-50 text-slate-600 transition-colors shadow-sm">
                                    <span class="material-icons-outlined text-base">filter_list</span>
                                </button>
                                <span id="filterBadgeHw" class="absolute -top-1 -right-1 w-4 h-4 bg-blue-600 text-white text-[10px] font-bold rounded-full hidden items-center justify-center pointer-events-none">0</span>
                            </div>
                            <!-- Download Dropdown -->
                            <div class="relative" id="downloadDropdownWrapper">
                                <button onclick="toggleDownloadDropdown()" class="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-slate-100 rounded-full transition-colors" title="Exportieren">
                                    <span class="material-icons-outlined text-lg">download</span>
                                </button>
                                <div id="downloadDropdown" class="hidden w-52 bg-white border border-slate-200 rounded-lg shadow-lg py-1" style="position:fixed;z-index:9999;">
                                    <button onclick="downloadFilteredInventory(); closeDownloadDropdown()" class="w-full text-left px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 flex items-center gap-2">
                                        <span class="material-icons-outlined text-base text-slate-400">article</span>TXT — Lesbare Liste
                                    </button>
                                    <button onclick="downloadFilteredInventoryCSV(); closeDownloadDropdown()" class="w-full text-left px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 flex items-center gap-2">
                                        <span class="material-icons-outlined text-base text-slate-400">table_view</span>CSV — Import-Format
                                    </button>
                                </div>
                            </div>
                            <!-- Bulk Edit (bleibt, JS-gesteuert) -->
                            <button id="bulkEditBtn" class="hidden bg-white border border-slate-300 text-slate-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-slate-50 flex items-center gap-2 shadow-sm" onclick="openBulkEditModal()">
                                <span class="material-icons-outlined text-base">edit_note</span>
                                <span id="bulkEditCount">0</span>
                            </button>
                            <!-- Add Dropdown -->
                            <div id="addDropdownWrapper">
                                <button onclick="toggleAddDropdown()" class="bg-blue-600 text-white h-9 w-9 rounded-full hover:bg-blue-700 shadow-sm transition-colors flex items-center justify-center" title="Hinzufügen">
                                    <span class="material-icons-outlined text-xl">add</span>
                                </button>
                                <div id="addDropdown" class="hidden w-52 bg-white border border-slate-200 rounded-lg shadow-lg py-1" style="position:fixed;z-index:9999;">
                                    <button onclick="openPartNumberModal(null); closeAddDropdown()" class="w-full text-left px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 flex items-center gap-2">
                                        <span class="material-icons-outlined text-base text-slate-400">edit_note</span>Manuell (Einzeln)
                                    </button>
                                    <button onclick="openImportModal(); closeAddDropdown()" class="w-full text-left px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 flex items-center gap-2">
                                        <span class="material-icons-outlined text-base text-slate-400">upload_file</span>Massenimport (CSV)
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Filter Accordion Panel -->
                    <div id="filterPanelHw" class="hidden">
                        <div class="flex flex-wrap justify-end gap-2 pt-3 pb-1 border-t border-slate-100">
                            <!-- Typ -->
                            <div class="relative">
                                <select id="filterHwType" onchange="applyHardwareFilters()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Typ: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Produkt -->
                            <div class="relative">
                                <select id="filterHwProduct" onchange="applyHardwareFilters()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Produkt: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Status -->
                            <div class="relative">
                                <select id="filterHwStatus" onchange="applyHardwareFilters()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Status: Alle</option>
                                    <option value="Available">Verfügbar</option>
                                    <option value="Maintenance">Wartung</option>
                                    <option value="In Order">Bestellt</option>
                                    <option value="Decommissioned">Ausgemustert</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Ort -->
                            <div class="relative">
                                <select id="filterHwLocation" onchange="applyHardwareFilters()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Ort: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- STI-Nr. -->
                            <div class="relative">
                                <select id="filterHwStiNr" onchange="applyHardwareFilters()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">STI-Nr.: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Reset Button -->
                            <button id="filterResetHw" onclick="resetFiltersHw()" class="hidden items-center gap-1 pl-2 pr-3 py-1.5 rounded-full border border-red-200 bg-red-50 text-red-600 text-xs font-medium hover:bg-red-100 transition-colors">
                                <span class="material-icons-outlined text-sm">close</span> Zurücksetzen
                            </button>
                        </div>
                    </div>

                </div>

                <div class="overflow-x-auto border border-slate-200 rounded-lg">
                    <!-- Tabelle bleibt gleich -->
                    <table class="w-full min-w-[1200px] text-sm text-left" id="hardwareInventoryTable">
                        <thead class="bg-slate-50 text-slate-600 uppercase text-xs font-semibold">
                            <tr>
                                <th class="px-4 py-3 border-b w-10 text-center">
                                    <input type="checkbox" id="selectAllHardware" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 h-4 w-4" onclick="handleSelectAllHardware(this)">
                                </th>
                                <th class="px-6 py-3 border-b flex items-center">
                                    <button id="expandAllBtn" class="expand-all-btn" onclick="toggleAllRows()" title="Alle auf-/zuklappen">
                                        <span class="material-icons-outlined text-lg">unfold_more</span>
                                    </button>
                                    P/N
                                </th>
                                <th class="px-6 py-3 border-b">Name</th>
                                <th class="px-6 py-3 border-b">Hardware Type</th>
                                <th class="px-6 py-3 border-b">Product Type</th>
                                <th class="px-6 py-3 border-b">QTY</th>
                                <th class="px-6 py-3 border-b">Status Overview</th>
                                <th class="px-6 py-3 border-b text-right">Actions</th>
                            </tr>
                        </thead>
                        <tbody id="hardwareInventoryTbody" class="divide-y divide-slate-200 bg-white"></tbody>
                    </table>
                </div>
            </div>
        </div>
    </main>
</div>

<!-- --- MODALS --- -->

<!-- 1. Trolley Modal (Updated with View/Edit) -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="trolleyModal">
    <div class="modal-backdrop" onclick="closeTrolleyModal()"></div>
    <div class="modal-content" style="max-width: 72rem;">

        <!-- HEADER -->
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-200">
            <div>
                <p class="text-xl font-bold text-slate-800" id="trolleyModalTitle">Rüstwagen Details</p>
                <p class="text-xs text-slate-500 mt-1" id="trolleyModalSubtitle">Details anzeigen</p>
            </div>
            <div class="flex items-center gap-2">
                <button id="btnEditTrolley" onclick="toggleTrolleyEditMode(true)" class="text-slate-400 hover:text-blue-600 p-2 rounded-full hover:bg-slate-100 transition-colors" title="Bearbeiten">
                    <span class="material-icons-outlined">edit</span>
                </button>
                <button onclick="closeTrolleyModal()" class="text-slate-400 hover:text-slate-600 p-2 rounded-full hover:bg-slate-100"><span class="material-icons-outlined">close</span></button>
            </div>
        </div>

        <!-- ZWEISPALTIGER BODY -->
        <div class="flex-grow flex overflow-hidden min-h-0">

            <!-- LINKS: Inhaltsliste -->
            <div class="flex flex-col flex-grow min-w-0 overflow-hidden">
                <!-- Fester Header über der Liste -->
                <div class="flex-shrink-0 px-6 pt-4 pb-3 border-b border-slate-100">
                    <div class="flex justify-between items-center mb-3">
                        <h3 class="text-base font-semibold text-slate-800">Inhalt (Zuweisung)</h3>
                        <button id="btnAddTrolleyItem" class="text-sm text-blue-600 hover:text-blue-800 font-medium items-center hidden" type="button" onclick="addTrolleyItem()">
                            <span class="material-icons-outlined text-base mr-1">add</span>Add Hardware
                        </button>
                    </div>
                    <div class="grid grid-cols-12 gap-2 mb-2 px-2 text-xs font-semibold text-slate-500 uppercase">
                        <div class="col-span-2">P/N</div>
                        <div class="col-span-2">S/N</div>
                        <div class="col-span-3">Name</div>
                        <div class="col-span-1 text-center">Anz.</div>
                        <div class="col-span-3">Pos (Lvl/Box)</div>
                        <div class="col-span-1"></div>
                    </div>
                    <div id="trolleySearchWrapper">
                        <div class="relative">
                            <span class="material-icons-outlined absolute left-2 top-1.5 text-slate-400 text-sm pointer-events-none">search</span>
                            <input type="text" id="trolleySearchInput" placeholder="Suchen nach P/N, S/N, Name..."
                                   class="w-full pl-7 pr-3 py-1 border border-slate-200 rounded-lg text-sm focus:outline-none focus:border-blue-300 bg-slate-50"
                                   oninput="filterTrolleyItems(this.value)">
                        </div>
                    </div>
                </div>
                <!-- Scrollbarer Itembereich -->
                <div class="flex-grow overflow-y-auto custom-scrollbar px-6 py-3">
                    <div id="trolleyItemsContainer" class="space-y-2"></div>
                    <div id="trolleyEmptyState" class="hidden text-center py-8 text-slate-400 italic text-sm">
                        Dieser Rüstwagen ist leer.
                    </div>
                </div>
            </div>

            <!-- RECHTS: Metadaten + Akkordeons -->
            <div class="flex-shrink-0 w-80 border-l border-slate-200 overflow-y-auto custom-scrollbar">
                <form id="trolleyForm" class="p-5">
                    <input type="hidden" id="trolleyModalOriginalName">

                    <!-- Metadaten -->
                    <div class="space-y-4">
                        <div>
                            <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Name / ID</label>
                            <input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors" id="trolleyModalName" placeholder="z.B. PS001" required>
                        </div>
                        <div>
                            <label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="trolleyModalEngine">Engine Type</label>
                            <input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors" id="trolleyModalEngine" required>
                        </div>
                        <div>
                            <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Verantwortlicher</label>
                            <input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors" id="trolleyModalResponsible" placeholder="Name der verantwortlichen Person" list="responsiblePersonsList">
                            <datalist id="responsiblePersonsList"></datalist>
                        </div>
                    </div>

                    <!-- Bilder / Gesamtansicht -->
                    <div class="mt-5 pt-4 border-t border-slate-100">
                        <div class="flex justify-between items-center mb-2">
                            <label class="block text-xs font-semibold text-slate-500 uppercase">Gesamtbild</label>
                            <label id="btnAddTrolleyImage" class="hidden cursor-pointer text-xs text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1">
                                <span class="material-icons-outlined text-base">add_photo_alternate</span>Hinzufügen
                                <input type="file" accept="image/*" class="hidden" onchange="uploadTrolleyImage(this)">
                            </label>
                        </div>
                        <div id="trolleyImagesList" class="space-y-1"></div>
                        <div id="trolleyImagesEmpty" class="text-slate-400 italic text-sm">Keine Bilder.</div>
                    </div>

                    <!-- AKKORDEON 1: Lagerstruktur (2D-Ansicht, standardmäßig offen) -->
                    <div class="mt-4 pt-4 border-t border-slate-100" id="trolleyStorageGridSection">
                        <button type="button" onclick="toggleStorageGridAccordion()"
                                class="w-full flex items-center gap-1.5 hover:opacity-80 transition-opacity mb-2">
                            <span class="material-icons-outlined text-slate-400 text-base transition-transform duration-200" id="storageGridChevron" style="transform:rotate(90deg)">chevron_right</span>
                            <span class="text-xs font-semibold text-slate-500 uppercase tracking-wide">Lagerstruktur</span>
                        </button>
                        <div id="storageGridBody" class="mt-1">
                            <div id="storageGrid2D"></div>
                            <div id="storageGridEmpty" class="text-xs text-slate-400 italic py-2">Keine Positionsangaben vorhanden.</div>
                        </div>
                    </div>

                    <!-- AKKORDEON 2: Vollständigkeitsprüfung (standardmäßig zugeklappt) -->
                    <div id="trolleyCompletenessSection" class="mt-4 pt-4 border-t border-slate-100">
                        <button type="button" onclick="toggleCompletenessAccordion()"
                                class="w-full flex items-center justify-between gap-2 hover:opacity-80 transition-opacity">
                            <div class="flex items-center gap-1.5 flex-shrink-0">
                                <span class="material-icons-outlined text-slate-400 text-base transition-transform duration-200" id="completenessChevron" style="transform:rotate(0deg)">chevron_right</span>
                                <span class="text-xs font-semibold text-slate-500 uppercase tracking-wide">Vollständigkeit</span>
                            </div>
                            <div id="trolleyCompletenessStatus" class="text-xs text-slate-500 italic text-right truncate">Noch nie geprüft</div>
                        </button>
                        <div id="completenessBody" class="hidden mt-3">
                            <!-- Tabs -->
                            <div class="border-b border-slate-200 mb-3">
                                <div class="flex -mb-px">
                                    <button type="button" onclick="switchCompletenessTab('check')" id="completenessTabCheck"
                                            class="px-3 py-2 text-xs font-medium border-b-2 border-blue-500 text-blue-600 focus:outline-none">
                                        Neue Prüfung
                                    </button>
                                    <button type="button" onclick="switchCompletenessTab('history')" id="completenessTabHistory"
                                            class="px-3 py-2 text-xs font-medium border-b-2 border-transparent text-slate-500 hover:text-slate-700 focus:outline-none">
                                        Verlauf
                                    </button>
                                </div>
                            </div>
                            <!-- Tab: Neue Prüfung -->
                            <div id="completenessCheckPanel">
                                <div class="flex gap-2 mb-3">
                                    <div class="flex-1">
                                        <label class="block text-xs text-slate-500 mb-1">Datum</label>
                                        <input type="date" id="checkDate" class="w-full border border-slate-300 rounded-md text-xs px-2 py-1.5 focus:ring-blue-500 focus:border-blue-500">
                                    </div>
                                    <div class="flex-1">
                                        <label class="block text-xs text-slate-500 mb-1">Schicht</label>
                                        <select id="checkShift" class="w-full border border-slate-300 rounded-md text-xs px-2 py-1.5 bg-white focus:ring-blue-500 focus:border-blue-500">
                                            <option value="F">Frühschicht</option>
                                            <option value="S">Spätschicht</option>
                                        </select>
                                    </div>
                                </div>
                                <div class="mb-3">
                                    <label class="block text-xs text-slate-500 mb-1">Prüfer</label>
                                    <input type="text" id="checkPruefer" class="w-full border border-slate-300 rounded-md text-xs px-2 py-1.5 focus:ring-blue-500 focus:border-blue-500">
                                </div>
                                <div class="flex gap-2 mb-3">
                                    <button type="button" onclick="setCheckStatus('vollständig')" id="btnStatusVoll"
                                            class="flex-1 py-1.5 rounded-lg text-xs font-medium bg-green-100 text-green-700 border-2 border-green-400 transition-colors">
                                        ✓ Vollständig
                                    </button>
                                    <button type="button" onclick="setCheckStatus('unvollständig')" id="btnStatusUnvoll"
                                            class="flex-1 py-1.5 rounded-lg text-xs font-medium bg-white text-slate-500 border border-slate-300 hover:bg-slate-50 transition-colors">
                                        ⚠ Unvollständig
                                    </button>
                                </div>
                                <div id="missingItemsSection" class="hidden mb-3">
                                    <label class="block text-xs text-slate-500 mb-2">Fehlende Teile (aus BOM)</label>
                                    <div id="missingItemsList" class="space-y-1 max-h-32 overflow-y-auto border border-slate-200 rounded-lg p-2 bg-slate-50"></div>
                                </div>
                                <div class="mb-3">
                                    <label class="block text-xs text-slate-500 mb-1">Notiz (optional)</label>
                                    <input type="text" id="checkNotes" class="w-full border border-slate-300 rounded-md text-xs px-2 py-1.5 focus:ring-blue-500 focus:border-blue-500" placeholder="z.B. Artikel bestellt...">
                                </div>
                                <button type="button" onclick="saveCompletenessCheck()"
                                        class="w-full py-2 bg-blue-600 text-white rounded-lg text-xs font-semibold hover:bg-blue-700 transition-colors">
                                    Prüfung speichern
                                </button>
                            </div>
                            <!-- Tab: Verlauf -->
                            <div id="completenessHistoryPanel" class="hidden">
                                <div id="completenessHistoryList" class="space-y-2 max-h-56 overflow-y-auto pr-1"></div>
                            </div>
                        </div>
                    </div>

                </form>
            </div>
        </div>

        <!-- FOOTER -->
        <div class="flex-shrink-0 py-4 px-6 bg-slate-50 border-t border-slate-200 flex justify-between items-center rounded-b-xl">
            <button id="deleteTrolleyBtn" class="hidden px-4 py-2 text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 shadow-sm flex items-center transition-colors" type="button">
                <span class="material-icons-outlined text-base mr-1">delete</span>Delete
            </button>
            <div class="flex gap-3 ml-auto">
                <div id="trolleyViewButtons" class="flex gap-3">
                    <button class="px-4 py-2 text-slate-600 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 shadow-sm flex items-center gap-1" type="button" onclick="generateTrolleyPrintView()" title="Als PDF drucken">
                        <span class="material-icons-outlined text-base">print</span>PDF / Druck
                    </button>
                    <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 shadow-sm" type="button" onclick="closeTrolleyModal()">Schließen</button>
                </div>
                <div id="trolleyEditButtons" class="flex gap-3 hidden">
                    <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 shadow-sm" type="button" onclick="toggleTrolleyEditMode(false)">Abbrechen</button>
                    <button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 shadow-sm flex items-center" type="submit" form="trolleyForm"><span class="material-icons-outlined text-base mr-1">save</span>Speichern</button>
                </div>
            </div>
        </div>

    </div>
</div>

<!-- Vollständigkeits-Prüfprotokoll Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[998]" id="completenessProtocolModal">
    <div class="modal-backdrop" onclick="closeCompletenessProtocolModal()"></div>
    <div class="modal-content" style="max-width: 62rem;">

        <!-- Header -->
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-200">
            <div class="flex items-center gap-2">
                <button id="btnProtocolBack" onclick="showProtocolHeatmapView()" class="hidden text-slate-400 hover:text-slate-700 p-1.5 rounded-full hover:bg-slate-100 transition-colors">
                    <span class="material-icons-outlined text-xl">arrow_back</span>
                </button>
                <div>
                    <p class="text-lg font-bold text-slate-800" id="protocolModalTitle">Vollständigkeits-Prüfprotokoll</p>
                    <p class="text-xs text-slate-500 mt-0.5" id="protocolModalSubtitle">Wochenübersicht Mo–Fr</p>
                </div>
            </div>
            <div class="flex items-center gap-2">
                <button onclick="showProtocolSettingsView()" id="btnProtocolSettings" class="text-slate-400 hover:text-blue-600 p-2 rounded-full hover:bg-slate-100 transition-colors" title="Einstellungen">
                    <span class="material-icons-outlined">settings</span>
                </button>
                <button onclick="closeCompletenessProtocolModal()" class="text-slate-400 hover:text-slate-600 p-2 rounded-full hover:bg-slate-100">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
        </div>

        <!-- HEATMAP VIEW -->
        <div id="protocolHeatmapView" class="flex-grow flex flex-col overflow-hidden min-h-0">
            <!-- Woche Navigation -->
            <div class="flex-shrink-0 flex items-center justify-between px-6 py-3 border-b border-slate-100 bg-white">
                <button onclick="navigateProtocolWeek(-1)" class="p-1.5 text-slate-400 hover:text-slate-700 hover:bg-slate-100 rounded-full transition-colors">
                    <span class="material-icons-outlined">chevron_left</span>
                </button>
                <span class="text-sm font-semibold text-slate-700" id="protocolWeekLabel">KW –</span>
                <button onclick="navigateProtocolWeek(1)" class="p-1.5 text-slate-400 hover:text-slate-700 hover:bg-slate-100 rounded-full transition-colors">
                    <span class="material-icons-outlined">chevron_right</span>
                </button>
            </div>
            <!-- Grid -->
            <div class="flex-grow overflow-y-auto custom-scrollbar px-6 py-5">
                <div id="protocolHeatmapGrid"></div>
                <div id="protocolHeatmapEmpty" class="hidden text-center py-16 text-slate-400 italic text-sm">
                    Keine Container für Vollständigkeitsprüfung konfiguriert.<br>
                    <button onclick="showProtocolSettingsView()" class="mt-3 text-blue-600 hover:underline text-sm not-italic">Jetzt konfigurieren →</button>
                </div>
            </div>
            <!-- Detail Panel -->
            <div id="protocolDetailPanel" class="hidden flex-shrink-0 border-t border-slate-200 px-6 py-4 bg-slate-50">
                <p class="text-xs font-semibold text-slate-500 uppercase mb-2" id="protocolDetailTitle">Details</p>
                <div id="protocolDetailList" class="space-y-1 max-h-36 overflow-y-auto"></div>
            </div>
        </div>

        <!-- SETTINGS VIEW -->
        <div id="protocolSettingsView" class="hidden flex-grow overflow-y-auto custom-scrollbar px-6 py-5">
            <p class="text-xs font-semibold text-slate-500 uppercase mb-1">Startdatum</p>
            <p class="text-xs text-slate-400 mb-3">Tage vor diesem Datum werden im Kalender nicht als fehlend (rot) gewertet.</p>
            <div class="mb-6 flex items-center gap-3">
                <input type="date" id="protocolStartDate" class="border border-slate-300 rounded-md text-sm px-3 py-1.5 focus:ring-blue-500 focus:border-blue-500">
                <button type="button" onclick="document.getElementById('protocolStartDate').value=''" class="text-xs text-slate-400 hover:text-slate-600">Kein Startdatum</button>
            </div>
            <p class="text-xs font-semibold text-slate-500 uppercase mb-1">Container & Prüfhäufigkeit</p>
            <p class="text-xs text-slate-400 mb-4">Lagerorte und Rüstwagen ohne Pflichtprüfung einfach deaktivieren.</p>
            <div id="protocolSettingsList" class="space-y-1.5"></div>
            <div class="mt-6 flex justify-end">
                <button onclick="saveCompletenessConfig()" class="px-5 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 transition-colors">
                    Einstellungen speichern
                </button>
            </div>
        </div>

        <!-- Footer Legende -->
        <div class="flex-shrink-0 py-3 px-6 bg-slate-50 border-t border-slate-200 flex items-center gap-5 rounded-b-xl">
            <div class="flex items-center gap-1.5"><div class="w-4 h-4 rounded bg-green-100 border border-green-300 flex-shrink-0"></div><span class="text-xs text-slate-500">Vollständig</span></div>
            <div class="flex items-center gap-1.5"><div class="w-4 h-4 rounded bg-orange-100 border border-orange-300 flex-shrink-0"></div><span class="text-xs text-slate-500">Teilweise / Unvollständig</span></div>
            <div class="flex items-center gap-1.5"><div class="w-4 h-4 rounded bg-red-100 border border-red-300 flex-shrink-0"></div><span class="text-xs text-slate-500">Fehlt</span></div>
            <div class="flex items-center gap-1.5"><div class="w-4 h-4 rounded bg-slate-100 border border-slate-200 flex-shrink-0"></div><span class="text-xs text-slate-500">Ausstehend</span></div>
        </div>
    </div>
</div>

<!-- 1b. Checkout Reason Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[1100]" id="checkoutReasonModal">
    <div class="modal-overlay absolute w-full h-full bg-slate-900/60 backdrop-blur-sm" onclick="closeCheckoutReasonModal()"></div>
    <div class="relative bg-white rounded-xl shadow-2xl w-full max-w-sm mx-4">
        <div class="px-6 pt-5 pb-3">
            <h3 class="text-base font-semibold text-slate-800 mb-1">Check-out bestätigen</h3>
            <p id="checkoutReasonItemLabel" class="text-xs text-slate-500 font-mono mb-4 truncate"></p>
            <label class="block text-xs font-medium text-slate-600 mb-1">
                Verwendungszweck / Ziel <span class="text-slate-400 font-normal">(optional)</span>
            </label>
            <input type="text" id="checkoutReasonInput" placeholder="z.B. Prüfstand 3 / Engine Run XYZ"
                   class="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:border-blue-400"
                   onkeydown="if(event.key==='Enter') confirmCheckOut()">
        </div>
        <div class="px-6 pb-5 flex justify-end gap-3">
            <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 text-sm" onclick="closeCheckoutReasonModal()">Abbrechen</button>
            <button class="px-4 py-2 text-white bg-orange-500 rounded-lg hover:bg-orange-600 text-sm flex items-center gap-1" onclick="confirmCheckOut()">
                <span class="material-icons-outlined text-base">logout</span>Check-out
            </button>
        </div>
    </div>
</div>

<!-- 1c. Check-in Confirmation Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[1100]" id="checkInConfirmModal">
    <div class="modal-overlay absolute w-full h-full bg-slate-900/60 backdrop-blur-sm" onclick="closeCheckInModal()"></div>
    <div class="relative bg-white rounded-xl shadow-2xl w-full max-w-sm mx-4">
        <div class="px-6 pt-5 pb-3">
            <h3 class="text-base font-semibold text-slate-800 mb-1">Check-in bestätigen</h3>
            <p id="checkInItemLabel" class="text-xs text-slate-500 font-mono mb-3 truncate"></p>
            <div id="checkInStatusInfo" class="text-xs text-orange-800 bg-orange-50 border border-orange-200 rounded-lg px-3 py-2"></div>
        </div>
        <div class="px-6 pb-5 flex justify-end gap-3">
            <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 text-sm" onclick="closeCheckInModal()">Abbrechen</button>
            <button class="px-4 py-2 text-white bg-green-600 rounded-lg hover:bg-green-700 text-sm flex items-center gap-1" onclick="confirmCheckIn()">
                <span class="material-icons-outlined text-base">login</span>Check-in
            </button>
        </div>
    </div>
</div>

<!-- 2. Part Number Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="partNumberModal">
    <div class="modal-backdrop" onclick="closePartNumberModal()"></div>
    <div class="modal-content">
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-200">
            <div>
                <p class="text-xl font-bold text-slate-800" id="partNumberModalTitle">Part Details</p>
                <p class="text-xs text-slate-500 mt-1" id="partNumberModalSubtitle">Details anzeigen</p>
            </div>
            <div class="flex items-center gap-2">
                <!-- Edit Button (Nur im View Mode) -->
                <button id="btnEditPart" onclick="togglePartNumberEditMode(true)" class="text-slate-400 hover:text-blue-600 p-2 rounded-full hover:bg-slate-100 transition-colors" title="Bearbeiten">
                    <span class="material-icons-outlined text-lg">edit</span>
                </button>
                <button onclick="closePartNumberModal()" class="text-slate-400 hover:text-slate-600 p-2 rounded-full hover:bg-slate-100 transition-colors">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
        </div>
        <div class="flex-grow overflow-y-auto custom-scrollbar p-6">
            <form id="partNumberForm" class="space-y-6">
                <input type="hidden" id="pnModal_originalPartNumber">
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="pnModal_partNumber">P/N (Part Number)</label><input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" id="pnModal_partNumber" required></div>
                    <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="pnModal_hardwareType">Hardware Type</label><input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" id="pnModal_hardwareType"></div>
                </div>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                     <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="pnModal_name">Name</label><input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" id="pnModal_name"></div>
                     <div>
                        <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Product Type(s)</label>
                        <div class="relative">
                            <div class="tag-input-wrapper" id="pnModal_productTags">
                                <input type="text" id="pnModal_productInput" class="tag-input-field" placeholder="Add type...">
                            </div>
                            <div id="pnModal_productDropdown" class="tag-dropdown"></div>
                        </div>
                     </div>
                </div>
                <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="pnModal_description">Description</label><textarea class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" id="pnModal_description" rows="2"></textarea></div>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="pnModal_stiNr">STI-Nr.</label><input type="number" min="0" step="1" class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" id="pnModal_stiNr" placeholder="Optional"></div>
                </div>

                <div class="pt-6 border-t border-slate-200">
                    <div class="flex justify-between items-center mb-3">
                        <h3 class="text-lg font-semibold text-slate-800">Serial Numbers</h3>
                        <button id="btnAddSerial" class="text-sm text-blue-600 hover:text-blue-800 font-medium flex items-center" type="button" onclick="addSerialNumberBlock()"><span class="material-icons-outlined text-base mr-1">add</span>Add Serial Number</button>
                    </div>
                    <div id="serialNumbersContainer" class="space-y-3"></div>
                </div>
            </form>
        </div>
        <div class="flex-shrink-0 py-4 px-6 bg-slate-50 border-t border-slate-200 flex justify-between items-center rounded-b-xl">
            <!-- Delete Button (Nur im Edit Mode sichtbar) -->
            <button id="deletePartBtn" class="hidden px-4 py-2 text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 shadow-sm flex items-center transition-colors" type="button">
                <span class="material-icons-outlined text-base mr-1">delete</span>Delete
            </button>
            
            <div class="flex gap-3 ml-auto">
                <!-- View Mode Buttons -->
                <div id="partViewButtons" class="flex gap-3">
                     <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 shadow-sm" type="button" onclick="closePartNumberModal()">Schließen</button>
                </div>
                
                <!-- Edit Mode Buttons -->
                <div id="partEditButtons" class="flex gap-3 hidden">
                    <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 shadow-sm" type="button" onclick="togglePartNumberEditMode(false)">Abbrechen</button>
                    <button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 shadow-sm flex items-center" type="submit" form="partNumberForm"><span class="material-icons-outlined text-base mr-1">save</span>Speichern</button>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- 3. New Mapping Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="newMappingModal">
    <div class="modal-backdrop" onclick="closeNewMappingModal()"></div>
    <div class="modal-content" style="max-width: 32rem;">
        <div class="py-4 px-6 border-b border-slate-200 flex justify-between items-center">
            <p class="text-xl font-bold text-slate-800">New Engine-Kit Mapping</p>
            <button class="text-slate-400 hover:text-slate-600" onclick="closeNewMappingModal()"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6">
            <form id="newMappingForm" class="space-y-6">
                <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="newEngineType">New Engine Type</label><input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500" id="newEngineType" required placeholder="e.g. LEAP-1A"></div>
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="newKitNameInput">Add Kit Names</label>
                    <div class="flex gap-2">
                        <input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500" id="newKitNameInput" placeholder="e.g., Kit-LEAP-Main">
                        <button class="bg-slate-100 border border-slate-300 px-4 py-2 rounded-lg text-slate-700 hover:bg-slate-200" type="button" id="addKitTagBtn">Add</button>
                    </div>
                    <div id="newKitTagsContainer" class="mt-3 flex flex-wrap gap-2 min-h-[2rem]"></div>
                </div>
            </form>
        </div>
        <div class="py-4 px-6 bg-slate-50 border-t border-slate-200 flex justify-end gap-3 rounded-b-xl">
            <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg shadow-sm hover:bg-slate-50" type="button" onclick="closeNewMappingModal()">Cancel</button>
            <button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 shadow-sm flex items-center gap-2" type="submit" form="newMappingForm"><span class="material-icons-outlined text-base">add_circle</span><span>Create</span></button>
        </div>
    </div>
</div>

<!-- 4. Edit Mapping Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="editMappingModal">
    <div class="modal-backdrop" onclick="closeEditMappingModal()"></div>
    <div class="modal-content" style="max-width: 32rem;">
        <div class="py-4 px-6 border-b border-slate-200 flex justify-between items-center">
            <p class="text-xl font-bold text-slate-800" id="editMappingModalTitle">Edit Mapping</p>
            <button class="text-slate-400 hover:text-slate-600" onclick="closeEditMappingModal()"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6">
            <form id="editMappingForm" class="space-y-6">
                <input type="hidden" id="originalEngineType">
                <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="editEngineType">Engine Type</label><input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500" id="editEngineType" required></div>
                <div><label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Associated Kits</label><div id="editKitTagsContainer" class="mt-2 flex flex-wrap gap-2 border border-slate-200 bg-slate-50 p-3 rounded-lg min-h-[4rem]"></div></div>
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1" for="editKitNameInput">Add New Kit</label>
                    <div class="flex gap-2">
                        <input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500" id="editKitNameInput" placeholder="Add another kit...">
                        <button class="bg-slate-100 border border-slate-300 px-4 py-2 rounded-lg text-slate-700 hover:bg-slate-200" type="button" id="addKitTagToEditBtn">Add</button>
                    </div>
                </div>
            </form>
        </div>
        <div class="py-4 px-6 bg-slate-50 border-t border-slate-200 flex justify-between items-center rounded-b-xl">
            <!-- Delete Button -->
            <button id="deleteMappingBtn" class="px-4 py-2 text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 shadow-sm flex items-center transition-colors" type="button">
                <span class="material-icons-outlined text-base mr-1">delete</span>Delete
            </button>
            <div class="flex gap-3">
                <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg shadow-sm hover:bg-slate-50" type="button" onclick="closeEditMappingModal()">Cancel</button>
                <button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 shadow-sm flex items-center gap-2" type="submit" form="editMappingForm"><span class="material-icons-outlined text-base">save</span><span>Save Changes</span></button>
            </div>
        </div>
    </div>
</div>

<!-- 5. BOM Modal (Updated with View/Edit Mode) -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="bomModal">
    <div class="modal-backdrop" onclick="closeBomModal()"></div>
    <div class="modal-content">
        
        <!-- Header -->
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-200">
            <div class="flex-grow mr-4">
                <p class="text-xl font-bold text-slate-800">Bill of Materials</p>
                <div class="flex items-center mt-1">
                    <span class="text-sm text-slate-500 mr-2 flex-shrink-0">Kit Name:</span>
                    <!-- Input: Classes werden per JS getoggled -->
                    <input type="text" id="modalKitNameInput"
                           class="font-mono font-medium text-slate-700 bg-transparent border-b border-transparent w-full text-sm py-0.5 px-1 rounded-sm transition-all outline-none"
                           placeholder="Kit Name eingeben..." readonly>
                </div>
                <div class="flex items-center mt-1">
                    <span class="text-sm text-slate-500 mr-2 flex-shrink-0">Code:</span>
                    <input type="text" id="modalKitCodeInput"
                           maxlength="6"
                           class="font-mono font-medium text-slate-700 bg-transparent border-b border-transparent w-20 text-sm py-0.5 px-1 rounded-sm transition-all outline-none uppercase"
                           placeholder="z.B. C01" readonly>
                    <span class="text-xs text-slate-400 ml-2">(max. 6 Zeichen, wird in der Timeline angezeigt)</span>
                </div>
                <span id="modalKitId" class="hidden"></span>
            </div>
            
            <div class="flex items-center gap-2">
                <!-- Edit Button (Nur im View Mode sichtbar) -->
                <button id="btnEditBom" onclick="toggleBomEditMode(true)" class="text-slate-400 hover:text-blue-600 p-2 rounded-full hover:bg-slate-100 transition-colors" title="Bearbeiten">
                    <span class="material-icons-outlined text-lg">edit</span>
                </button>
                <button onclick="closeBomModal()" class="text-slate-400 hover:text-slate-600 p-2 rounded-full hover:bg-slate-100 transition-colors">
                    <span class="material-icons-outlined">close</span>
                </button>
            </div>
        </div>

        <!-- Body -->
        <div class="flex-grow overflow-y-auto custom-scrollbar p-6">
            <form id="bomForm">
                <div class="space-y-2" id="bomItemsContainer">
                    <!-- Items inserted via JS -->
                </div>
                
                <!-- Add Button (Nur im Edit Mode sichtbar) -->
                <div class="mt-6 pt-4 border-t border-slate-100 hidden" id="bomAddContainer">
                    <button class="text-sm text-blue-600 hover:text-blue-800 font-medium flex items-center px-2 py-1 rounded hover:bg-blue-50 transition-colors inline-flex" onclick="addBomItem()" type="button">
                        <span class="material-icons-outlined text-base mr-1">add_circle_outline</span> Add Item
                    </button>
                </div>
            </form>
        </div>

        <!-- Footer -->
        <div class="flex-shrink-0 py-4 px-6 bg-slate-50 border-t border-slate-200 flex justify-end gap-3 rounded-b-xl">
            
            <!-- View Mode Footer -->
            <div id="bomViewFooter">
                <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg shadow-sm hover:bg-slate-50" type="button" onclick="closeBomModal()">Schließen</button>
            </div>

            <!-- Edit Mode Footer (Hidden by default) -->
            <div id="bomEditFooter" class="hidden flex gap-3">
                <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg shadow-sm hover:bg-slate-50" type="button" onclick="toggleBomEditMode(false)">Abbrechen</button>
                <button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 shadow-sm flex items-center gap-2" type="submit" form="bomForm">
                    <span class="material-icons-outlined text-base">save</span> Speichern
                </button>
            </div>
        </div>
    </div>
</div>

<!-- 6. Bulk Edit Modal -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="bulkEditModal">
    <div class="modal-backdrop" onclick="closeBulkEditModal()"></div>
    <div class="modal-content" style="max-width: 30rem;">
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-200">
            <p class="text-xl font-bold text-slate-800">Bulk Edit</p>
            <button onclick="closeBulkEditModal()" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6">
            <p class="text-sm text-slate-500 mb-4">Änderungen werden auf <strong id="bulkEditCountLabel">0</strong> Einträge angewendet. Leere Felder werden ignoriert (nicht geändert).</p>
            <form id="bulkEditForm" class="space-y-4">
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Hardware Type</label>
                    <input class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-blue-500" id="bulkHardwareType" placeholder="Unverändert lassen...">
                </div>
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Product Type(s)</label>
                    <!-- Wir nutzen hier die gleiche Tag-Input Struktur wie im Einzel-Edit -->
                    <div class="relative">
                        <div class="tag-input-wrapper" id="bulkProductTags">
                            <input type="text" id="bulkProductInput" class="tag-input-field" placeholder="Typen hinzufügen (überschreibt!)...">
                        </div>
                        <div id="bulkProductDropdown" class="tag-dropdown"></div>
                    </div>
                    <p class="text-xs text-amber-600 mt-1">Hinweis: Setzt den Product Type für alle gewählten Teile neu.</p>
                </div>
            </form>
        </div>
        <div class="flex-shrink-0 py-4 px-6 bg-slate-50 border-t border-slate-200 flex justify-end gap-3 rounded-b-xl">
            <button class="px-4 py-2 text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 shadow-sm" onclick="closeBulkEditModal()">Cancel</button>
            <button class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 shadow-sm flex items-center" onclick="submitBulkEdit()">
                <span class="material-icons-outlined text-base mr-1">done_all</span>Apply
            </button>
        </div>
    </div>
</div>

<!-- IMPORT MODAL -->
<div class="modal hidden fixed w-full h-full top-0 left-0 flex items-center justify-center z-[999]" id="importModal">
    <div class="modal-backdrop" onclick="closeImportModal()"></div>
    <div class="modal-content w-full max-w-3xl mx-4">
        <!-- Header -->
        <div class="flex-shrink-0 py-4 px-6 flex justify-between items-center border-b border-slate-200">
            <div>
                <p class="text-xl font-bold text-slate-800">Massenimport</p>
                <p class="text-xs text-slate-500 mt-1">CSV-Datei hochladen — Duplikate werden automatisch erkannt</p>
            </div>
            <button onclick="closeImportModal()" class="text-slate-400 hover:text-slate-600 p-2 rounded-full hover:bg-slate-100">
                <span class="material-icons-outlined">close</span>
            </button>
        </div>

        <!-- Body -->
        <div class="flex-grow overflow-y-auto custom-scrollbar p-6 space-y-5">

            <!-- Step 1: Template -->
            <div class="flex items-center gap-4 p-4 bg-slate-50 rounded-lg border border-slate-200">
                <span class="material-icons-outlined text-slate-400 text-2xl">file_download</span>
                <div class="flex-1">
                    <p class="text-sm font-semibold text-slate-700">Schritt 1: Vorlage herunterladen</p>
                    <p class="text-xs text-slate-500 mt-0.5">CSV-Vorlage mit den korrekten Spalten — pro Seriennummer eine Zeile</p>
                </div>
                <button onclick="downloadImportTemplate()" class="text-sm px-3 py-1.5 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 font-medium text-slate-700 flex items-center gap-1.5 transition-colors">
                    <span class="material-icons-outlined text-base">download</span>Vorlage (.csv)
                </button>
            </div>

            <!-- Step 2: Upload -->
            <div>
                <p class="text-sm font-semibold text-slate-700 mb-2">Schritt 2: Ausgefüllte CSV hochladen</p>
                <label class="flex flex-col items-center justify-center w-full h-28 border-2 border-dashed border-slate-300 rounded-lg cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition-colors" id="importDropZone">
                    <span class="material-icons-outlined text-3xl text-slate-400">upload_file</span>
                    <span class="text-sm text-slate-500 mt-1">CSV-Datei auswählen oder hier ablegen</span>
                    <input type="file" accept=".csv" class="hidden" id="importFileInput" onchange="handleImportFile(this)">
                </label>
            </div>

            <!-- Preview Table -->
            <div id="importPreviewSection" class="hidden">
                <div class="flex items-center justify-between mb-2">
                    <p class="text-sm font-semibold text-slate-700">Schritt 3: Vorschau &amp; Bestätigen</p>
                    <div id="importStats" class="flex gap-2 text-xs"></div>
                </div>
                <div class="overflow-x-auto border border-slate-200 rounded-lg max-h-64 overflow-y-auto custom-scrollbar">
                    <table class="w-full text-xs text-left">
                        <thead class="bg-slate-50 text-slate-500 uppercase font-semibold sticky top-0">
                            <tr>
                                <th class="px-3 py-2">Status</th>
                                <th class="px-3 py-2">P/N</th>
                                <th class="px-3 py-2">Name</th>
                                <th class="px-3 py-2">Type</th>
                                <th class="px-3 py-2">Product Type</th>
                                <th class="px-3 py-2">S/N</th>
                                <th class="px-3 py-2">Location</th>
                                <th class="px-3 py-2">Status (S/N)</th>
                            </tr>
                        </thead>
                        <tbody id="importPreviewTbody"></tbody>
                    </table>
                </div>
                <p id="importErrorMsg" class="hidden mt-2 text-xs text-red-600 font-medium"></p>
            </div>

        </div>

        <!-- Footer -->
        <div class="flex-shrink-0 py-4 px-6 flex justify-end gap-3 border-t border-slate-200">
            <button onclick="closeImportModal()" class="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50">Abbrechen</button>
            <button id="importConfirmBtn" onclick="executeImport()" disabled class="px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-2">
                <span class="material-icons-outlined text-base">check</span>
                <span id="importConfirmLabel">Importieren</span>
            </button>
        </div>
    </div>
</div>

<!-- LIGHTBOX MODAL (Kopie aus Maintenance) -->
<div id="lightboxModal" class="modal hidden fixed inset-0 z-[9999] bg-black bg-opacity-90 flex items-center justify-center backdrop-blur-sm transition-opacity duration-300" onclick="closeLightbox()">
    <div class="relative max-w-full max-h-full p-4 flex flex-col items-center justify-center">
        <img id="lightboxImage" src="" class="max-h-[85vh] max-w-[90vw] object-contain rounded shadow-2xl cursor-default" onclick="event.stopPropagation()">
        <button class="absolute top-6 right-6 text-white bg-white/10 hover:bg-white/20 rounded-full p-2 transition-colors backdrop-blur-md" onclick="closeLightbox()">
            <span class="material-icons-outlined text-2xl">close</span>
        </button>
        <p id="lightboxCaption" class="mt-4 text-slate-200 text-sm font-medium tracking-wide bg-black/50 px-4 py-1 rounded-full"></p>
    </div>
</div>

<script>
  // --- ROLE CONSTANTS ---
  const isTestMechanic = {{ 'true' if 'TESTMECHANIC' in session.get('roles', []) and not session.get('is_admin') else 'false' }};
  const isCurrentUserAdmin = {{ 'true' if session.get('is_admin') else 'false' }};

  // --- INITIAL DATA LOAD FROM SERVER ---
  let dataVersionHash = "{{ hardware_version_hash }}";
  let hardwareInventory = {{ inventory_json | safe }};
  let kitBoms = {{ boms_json | safe }};
  let engineMappings = {{ mappings_json | safe }};
  const bulkEditModal = document.getElementById('bulkEditModal');
  let selectedPartNumbers = new Set();
  let currentBulkProductTags = []; // Für das Bulk Tag Input
  if (Array.isArray(engineMappings) || !engineMappings) engineMappings = {};
  let trolleys = {{ trolleys_json | safe }};
  if (Array.isArray(trolleys) || !trolleys) trolleys = {};
  let storageUnits = {{ storage_units_json | safe }};
  if (Array.isArray(storageUnits) || !storageUnits) storageUnits = {};
  const allEmployeeNames = {{ employees_json | safe }};
  let isTrolleyEditMode = false;
  let currentEditType = 'trolley'; // 'trolley' oder 'storage'

  // Sort state for the three small tables: null → 'asc' → 'desc' → null
  const tableSortState = {
    mapping: { col: null, dir: null },
    trolley:  { col: null, dir: null },
    storage:  { col: null, dir: null }
  };

  function cycleSortDir(tableKey, col) {
    const s = tableSortState[tableKey];
    if (s.col !== col || s.dir === null) { s.col = col; s.dir = 'asc'; }
    else if (s.dir === 'asc')            { s.dir = 'desc'; }
    else                                 { s.col = null; s.dir = null; }
  }

  function sortIcon(tableKey, col) {
    const s = tableSortState[tableKey];
    if (s.col !== col || s.dir === null)
      return '<span class="material-icons-outlined" style="font-size:14px;color:#cbd5e1;vertical-align:middle;line-height:1;">swap_vert</span>';
    if (s.dir === 'asc')
      return '<span class="material-icons-outlined" style="font-size:14px;color:#3b82f6;vertical-align:middle;line-height:1;">arrow_upward</span>';
    return '<span class="material-icons-outlined" style="font-size:14px;color:#3b82f6;vertical-align:middle;line-height:1;">arrow_downward</span>';
  }

  function sortedKeys(obj, tableKey) {
    const s = tableSortState[tableKey];
    const keys = Object.keys(obj);
    if (s.col === null || s.dir === null) return keys;
    const col = s.col;
    return keys.slice().sort((a, b) => {
      let va, vb;
      if (tableKey === 'mapping') {
        va = a; vb = b; // only col 0 (engine name)
      } else {
        va = col === 0 ? (obj[a].engineType || '') : a;
        vb = col === 0 ? (obj[b].engineType || '') : b;
      }
      const cmp = va.localeCompare(vb, 'de', { sensitivity: 'base' });
      return s.dir === 'asc' ? cmp : -cmp;
    });
  }
  const CURRENT_USER_DISPLAY_NAME = "{{ current_user_display_name }}";
  var pendingCheckout = null;
  var pendingCheckin = null;
  let currentlyEditingEntryId = null;
  let currentCompletenessLog = [];
  let currentLevelImages = {}; // { "1": {path, name, stored_name}, ... }
  let completenessConfig = {{ completeness_config_json | safe }};
  let protocolWeekOffset = 0;
  let protocolLog = [];
    // --- DOKUMENTE & LIGHTBOX ---
    
    function openLightbox(url, caption) {
        const m = document.getElementById('lightboxModal');
        const i = document.getElementById('lightboxImage');
        const c = document.getElementById('lightboxCaption');
        i.src = url; c.textContent = caption;
        m.classList.remove('hidden');
    }
    function closeLightbox() { document.getElementById('lightboxModal').classList.add('hidden'); }
    
    async function uploadHardwareFile(inputElement) {
        const file = inputElement.files[0];
        if(!file) return;
        
        const container = inputElement.closest('.docs-container');
        const list = container.querySelector('.docs-list');
        
        const formData = new FormData();
        formData.append('file', file);
        
        try {
            const res = await fetch('/api/hardware/upload', { method: 'POST', body: formData });
            if(!res.ok) throw new Error();
            const data = await res.json();
            
            addDocItem(list, data.path, data.filename, data.stored_name);
            inputElement.value = ''; // Reset
        } catch(e) {
            alert("Upload fehlgeschlagen.");
        }
    }
    
    function addDocItem(listElement, path, name, storedName) {
        const div = document.createElement('div');
        div.className = "flex items-center justify-between p-2 bg-white border border-slate-200 rounded mb-1 text-sm doc-item";
        // Daten im DOM speichern für den späteren Save
        div.dataset.path = path;
        div.dataset.name = name;
        div.dataset.stored = storedName;
        
        const isImg = name.match(/\.(jpg|jpeg|png|gif|webp)$/i);
        let preview = `<span class="material-icons-outlined text-slate-400 mr-2">description</span>`;
        let clickAction = `window.open('${path}', '_blank')`;
        
        if(isImg) {
            preview = `<img src="${path}" class="w-6 h-6 object-cover rounded mr-2 border border-slate-100">`;
            clickAction = `openLightbox('${path}', '${name}')`;
        }
        
        div.innerHTML = `
            <div class="flex items-center cursor-pointer truncate" onclick="${clickAction}">
                ${preview}
                <span class="truncate" title="${name}">${name}</span>
            </div>
            <button type="button" onclick="this.parentElement.remove()" class="text-red-400 hover:text-red-600 ml-2">
                <span class="material-icons-outlined text-base">close</span>
            </button>
        `;
        listElement.appendChild(div);
    }
    
    async function saveDataToServer() {
      console.log("%c DEBUG: saveDataToServer gestartet...", "color: orange; font-weight: bold;");

      const payload = {
          inventory: hardwareInventory,
          boms: kitBoms,
          mappings: engineMappings,
          trolleys: trolleys,
          storage_units: storageUnits,
          version_hash: dataVersionHash
      };

      console.log("DEBUG: Sende folgenden Payload an Server:", payload);

      try {
          const response = await fetch('/api/test_equipment/save', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(payload)
          });

          console.log("DEBUG: Server HTTP Status:", response.status);

          if (response.status === 409) {
              alert('Konflikt: Die Daten wurden zwischenzeitlich von einem anderen Benutzer geändert.\\n\\nDie Seite wird jetzt neu geladen.');
              window.location.reload();
              return false;
          }

          if (!response.ok) {
              const errorText = await response.text();
              console.error("DEBUG: Server antwortete mit Fehler:", errorText);
              throw new Error('Server Fehler: ' + response.status);
          }

          const result = await response.json();
          console.log("DEBUG: Server Antwort JSON:", result);
          if (result.version_hash) dataVersionHash = result.version_hash;
          return true;

      } catch (error) {
          console.error('DEBUG: JS Fehler beim Fetch:', error);
          alert('Speichern fehlgeschlagen! Bitte Konsole (F12) prüfen.');
          return false;
      }
  }

  // --- INIT ---
  document.addEventListener('DOMContentLoaded', () => {
      const sidebar = document.getElementById('sidebar');
      const sidebarToggleBtn = document.getElementById('sidebar-toggle');
      if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    toggleBtnIcon.textContent = 'chevron_left';
                }
            };
            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
      }

      renderMappingTable();
      renderTrolleyTable();
      renderStorageTable();
      updateAndReassignKits();
      initHardwareFilters();
      if (isTestMechanic) document.getElementById('btnEditBom').style.display = 'none';
  });

  // --- GLOBALS ---
  const bomModal = document.getElementById('bomModal');
  const newMappingModal = document.getElementById('newMappingModal');
  const editMappingModal = document.getElementById('editMappingModal');
  const partNumberModal = document.getElementById('partNumberModal');
  const trolleyModal = document.getElementById('trolleyModal');
  const importModal = document.getElementById('importModal');
  const deletePartBtn = document.getElementById('deletePartBtn');
  const deleteTrolleyBtn = document.getElementById('deleteTrolleyBtn');
  const deleteMappingBtn = document.getElementById('deleteMappingBtn');
  const expandAllBtn = document.getElementById('expandAllBtn');

  let areAllRowsExpanded = false;
  var importParsedRows = [];

  // HILFSFUNKTIONEN
  function openModal(m) { m.classList.remove('hidden'); document.body.classList.add('modal-active'); }
  function closeModal(m) { m.classList.add('hidden'); document.body.classList.remove('modal-active'); }
  function addKitTag(name, container, onRemove = null) {
      if(name){
          const t = document.createElement('div');
          t.className = 'new-kit-tag flex items-center bg-white border border-slate-300 text-slate-700 text-xs font-medium mr-2 mb-2 pl-3 pr-2 py-1.5 rounded-full shadow-sm';
          t.dataset.kitName = name;
          t.innerHTML = `<span>${name}</span><button type="button" class="new-kit-tag-remove ml-2 text-slate-400 hover:text-red-500 flex items-center justify-center rounded-full p-0.5 transition-colors"><span class="material-icons-outlined text-sm">close</span></button>`;
          t.querySelector('button').onclick = onRemove ? () => onRemove(name, t) : () => t.remove();
          container.appendChild(t);
          return true;
      }
      return false;
  }

  function deleteKitCallback(kitName, tagEl) {
      const kitId = Object.keys(kitBoms).find(k => kitBoms[k] && kitBoms[k].name === kitName);
      if (!confirm(`Kit "${kitName}" dauerhaft löschen?\nEs wird aus allen Engine-Mappings entfernt und kann nicht wiederhergestellt werden.`)) return;
      if (kitId) {
          delete kitBoms[kitId];
          for (const eng in engineMappings) {
              engineMappings[eng] = (engineMappings[eng] || []).filter(id => id !== kitId);
          }
          saveDataToServer().then(() => renderMappingTable());
      }
      tagEl.remove();
  }

  // --- TAG INPUT LOGIK FÜR PRODUCT TYPE ---
  let currentProductTags = [];

  function renderProductTags() {
      const container = document.getElementById('pnModal_productTags');
      const input = document.getElementById('pnModal_productInput');

      // Entferne alle bisherigen Chips
      const chips = container.querySelectorAll('.tag-chip');
      chips.forEach(chip => chip.remove());

      currentProductTags.forEach(tag => {
          const chip = document.createElement('span');
          chip.className = 'tag-chip';
          chip.innerHTML = `${tag}<span class="tag-chip-remove" onclick="removeProductTag('${tag}')">×</span>`;
          container.insertBefore(chip, input);
      });

      input.value = '';
  }

  function addProductTag(tag) {
      if (tag && !currentProductTags.includes(tag)) {
          currentProductTags.push(tag);
          renderProductTags();
      }
      document.getElementById('pnModal_productDropdown').classList.add('hidden');
  }

  window.removeProductTag = function(tag) { 
      currentProductTags = currentProductTags.filter(t => t !== tag);
      renderProductTags();
  }

  function setupProductTagInput() {
      const input = document.getElementById('pnModal_productInput');
      const dropdown = document.getElementById('pnModal_productDropdown');

      const availableTypes = new Set(['N/A', 'Facility']);
      document.querySelectorAll('#engineKitMappingTable .engine-type-cell').forEach(cell => {
          availableTypes.add(cell.textContent.trim());
      });

      // Klonen um Listener-Stacking zu vermeiden
      const newInput = input.cloneNode(true);
      input.parentNode.replaceChild(newInput, input);

      newInput.addEventListener('input', () => {
          const val = newInput.value.toLowerCase();
          dropdown.innerHTML = '';

          const filtered = Array.from(availableTypes).filter(t => 
              t.toLowerCase().includes(val) && !currentProductTags.includes(t)
          );

          if (filtered.length > 0) {
              filtered.forEach(t => {
                  const div = document.createElement('div');
                  div.className = 'tag-dropdown-item';
                  div.textContent = t;
                  div.onclick = () => addProductTag(t);
                  dropdown.appendChild(div);
              });
              dropdown.classList.remove('hidden');
          } else {
              dropdown.classList.add('hidden');
          }
      });

      document.addEventListener('click', (e) => {
          if (!newInput.contains(e.target) && !dropdown.contains(e.target)) {
              dropdown.classList.add('hidden');
          }
      });

      newInput.addEventListener('keydown', (e) => {
          if (e.key === 'Enter') {
              e.preventDefault();
              addProductTag(newInput.value.trim());
          }
      });
  }

  function openStandardTrolleyModal(name) {
      currentEditType = 'trolley';
      openTrolleyModal(name);
  }

  function openStorageModal(name) {
      currentEditType = 'storage';
      openTrolleyModal(name);
  }
  function renderStorageTable() {
      const tbody = document.getElementById('storageTableBody');
      tbody.innerHTML = '';
      const si0 = document.getElementById('sort-storage-0');
      const si1 = document.getElementById('sort-storage-1');
      if (si0) si0.innerHTML = sortIcon('storage', 0);
      if (si1) si1.innerHTML = sortIcon('storage', 1);

      if (Object.keys(storageUnits).length === 0) {
           tbody.innerHTML = '<tr><td colspan="3" class="px-4 py-8 text-center text-slate-400 text-sm italic">Keine Lagerorte definiert.</td></tr>';
           return;
      }

      for (const name of sortedKeys(storageUnits, 'storage')) {
          const data = storageUnits[name];
          const tr = document.createElement('tr');
          tr.className = 'hover:bg-slate-50 border-b border-slate-100 last:border-0';

          tr.innerHTML = `
            <td class="px-4 py-3 font-medium text-slate-900">${data.engineType || '-'}</td>
            <td class="px-4 py-3"><span class="cursor-pointer bg-indigo-50 border border-indigo-200 text-indigo-700 px-3 py-1 rounded-full text-xs font-semibold" onclick="openStorageModal('${name}')">${name}</span></td>
            <td class="px-4 py-3 text-right">
                <button class="text-slate-400 hover:text-green-600 p-1 transition-colors" onclick="downloadTrolleyBOM('${name}')" title="Inhalt exportieren">
                    <span class="material-icons-outlined text-lg">download</span>
                </button>
            </td>
          `;
          tbody.appendChild(tr);
      }
  }

  // --- EXPAND ALL ---
  function toggleAllRows() {
      areAllRowsExpanded = !areAllRowsExpanded;
      const icon = expandAllBtn.querySelector('.material-icons-outlined');
      icon.textContent = areAllRowsExpanded ? 'unfold_less' : 'unfold_more';

      document.querySelectorAll('.toggle-sn-rows').forEach(btn => {
          if(areAllRowsExpanded) btn.classList.add('is-open'); else btn.classList.remove('is-open');
      });

      document.querySelectorAll('.sn-row').forEach(row => {
          const parentPn = row.dataset.parentPn;
          const parentRow = document.querySelector(`tr.pn-row[data-pn="${parentPn}"]`);
          if (parentRow && parentRow.style.display !== 'none') {
              if (areAllRowsExpanded) row.classList.add('is-visible'); else row.classList.remove('is-visible');
          }
      });
  }

  // --- RENDER MAPPINGS ---
    function renderMappingTable() {
        const tbody = document.querySelector('#engineKitMappingTable tbody');
        tbody.innerHTML = '';
        const si = document.getElementById('sort-mapping-0');
        if (si) si.innerHTML = sortIcon('mapping', 0);

        for (const engine of sortedKeys(engineMappings, 'mapping')) {
            const kitIds = engineMappings[engine]; // Das sind jetzt UUIDs (meistens)
            const tr = document.createElement('tr');
            tr.className = 'border-b hover:bg-slate-50 last:border-0';
            
            let kitsHtml = '';
            kitIds.forEach(kitId => {
                const bom = kitBoms[kitId];
                const kitName = bom ? bom.name : kitId;

                let statusClass = 'bg-kit-missing';
                if (bom && bom.status === 'ready') statusClass = 'bg-kit-ready';
                if (bom && bom.status === 'partial') statusClass = 'bg-kit-partial';

                const codeHtml = (bom && bom.code)
                    ? `<span style="font-size:0.65rem;background:#cffafe;color:#0e7490;border-radius:3px;padding:0 3px;margin-right:3px;font-family:monospace;font-weight:700;">${bom.code}</span>`
                    : '';

                kitsHtml += `<span class="kit-tag" data-kit-id="${kitId}" onclick="openBomModal('${kitId}')">
                                <span class="kit-status-dot ${statusClass}"></span>${codeHtml}${kitName}
                             </span>`;
            });
            
            tr.innerHTML = `
                <td class="px-4 py-3 font-medium engine-type-cell">${engine}</td>
                <td class="px-4 py-3 kit-cell">${kitsHtml}</td>
                <td class="px-4 py-3 text-right">
                    ${isTestMechanic ? '' : '<button class="text-slate-400 hover:text-blue-600 edit-mapping-btn"><span class="material-icons-outlined text-lg">edit</span></button>'}
                </td>`;
            tbody.appendChild(tr);
        }
    }

  // --- TROLLEY LOGIK ---
  function renderTrolleyTable() {
      const tbody = document.querySelector('#trolleyTableBody');
      tbody.innerHTML = '';
      const si0 = document.getElementById('sort-trolley-0');
      const si1 = document.getElementById('sort-trolley-1');
      if (si0) si0.innerHTML = sortIcon('trolley', 0);
      if (si1) si1.innerHTML = sortIcon('trolley', 1);

      if (Object.keys(trolleys).length === 0) {
           tbody.innerHTML = '<tr><td colspan="3" class="px-4 py-8 text-center text-slate-400 text-sm italic">Keine Rüstwagen definiert.</td></tr>';
           return;
      }

      for (const trolleyName of sortedKeys(trolleys, 'trolley')) {
          const data = trolleys[trolleyName];
          const tr = document.createElement('tr');
          tr.className = 'hover:bg-slate-50 border-b border-slate-100 last:border-0';

          tr.innerHTML = `
            <td class="px-4 py-3 font-medium text-slate-900">${data.engineType}</td>
            <td class="px-4 py-3"><span class="trolley-tag cursor-pointer bg-orange-50 border-orange-200 text-orange-700 px-3 py-1 rounded-full text-xs font-semibold" onclick="openStandardTrolleyModal('${trolleyName}')">${trolleyName}</span></td>
            <td class="px-4 py-3 text-right">
                <button class="text-slate-400 hover:text-green-600 p-1 transition-colors" onclick="downloadTrolleyBOM('${trolleyName}')" title="BOM Downloaden">
                    <span class="material-icons-outlined text-lg">download</span>
                </button>
            </td>
          `;
          tbody.appendChild(tr);
      }
  }

  
  function toggleTrolleyEditMode(enable) {
      isTrolleyEditMode = enable;
      
      // Header & Buttons umschalten
      document.getElementById('btnEditTrolley').style.display = enable ? 'none' : 'block';
      document.getElementById('trolleyEditButtons').classList.toggle('hidden', !enable);
      document.getElementById('trolleyViewButtons').classList.toggle('hidden', enable);
      
      // Delete Button nur wenn wir ein bestehendes Item bearbeiten (name ist gesetzt)
      const isNew = !document.getElementById('trolleyModalOriginalName').value;
      document.getElementById('deleteTrolleyBtn').classList.toggle('hidden', !enable || isNew);
      
      document.getElementById('trolleyModalSubtitle').textContent = enable ? 'Bearbeiten' : 'Details anzeigen';
      document.getElementById('btnAddTrolleyItem').classList.toggle('hidden', !enable);
      document.getElementById('btnAddTrolleyImage').classList.toggle('hidden', !enable);

      // Inputs umschalten
      const inputs = document.querySelectorAll('#trolleyForm input');
      inputs.forEach(inp => {
          if (inp.id === 'trolleySearchInput') return; // Suchfeld immer bedienbar
          if (inp.closest('#trolleyCompletenessSection')) return; // Completeness-Felder immer bedienbar
          inp.readOnly = !enable;
          if (enable) {
              inp.classList.remove('bg-slate-50', 'text-slate-600', 'border-transparent');
              inp.classList.add('bg-white', 'border-slate-300');
          } else {
              inp.classList.add('bg-slate-50', 'text-slate-600', 'border-transparent');
              inp.classList.remove('bg-white', 'border-slate-300');
          }
      });
      
      // Selects umschalten
      const selects = document.querySelectorAll('#trolleyItemsContainer select');
      selects.forEach(sel => {
          sel.disabled = !enable;
          sel.classList.toggle('bg-slate-50', !enable);
          sel.classList.toggle('appearance-none', !enable); // Pfeil verstecken im View Mode
      });

      // Löschen-Buttons in den Zeilen umschalten
      document.querySelectorAll('.trolley-item-delete-btn').forEach(btn => {
          btn.style.visibility = enable ? 'visible' : 'hidden';
      });
      document.querySelectorAll('.trolley-img-delete-btn').forEach(btn => {
          btn.style.visibility = enable ? 'visible' : 'hidden';
      });
      // Checkout-Sections: sichtbar im View-Mode, versteckt im Edit-Mode
      document.querySelectorAll('.trolley-checkout-section').forEach(el => {
          el.style.display = enable ? 'none' : 'flex';
      });
      // Level/Box-Inputs in Item-Zeilen umschalten (liegen außerhalb #trolleyForm)
      document.querySelectorAll('.trolley-level-input, .trolley-box-input').forEach(inp => {
          inp.readOnly = !enable;
          inp.classList.toggle('bg-slate-50', !enable);
          inp.classList.toggle('border-transparent', !enable);
          inp.classList.toggle('border-slate-300', enable);
      });
      // Lagerstruktur-Grid neu rendern damit Upload-Buttons erscheinen/verschwinden
      renderStorageGrid();
  }

function openTrolleyModal(name) {
      const form = document.getElementById('trolleyForm'); form.reset();
      document.getElementById('trolleyItemsContainer').innerHTML = '';
      document.getElementById('trolleyImagesList').innerHTML = '';
      const srch = document.getElementById('trolleySearchInput');
      if (srch) srch.value = '';
      const nameInput = document.getElementById('trolleyModalName');

      // Bestimmen der Quelle und Texte basierend auf currentEditType
      const dataSource = (currentEditType === 'storage') ? storageUnits : trolleys;
      const titlePrefix = (currentEditType === 'storage') ? 'Lagerort' : 'Rüstwagen';
      
      // Label dynamisch anpassen
      const engineLabel = document.querySelector('label[for="trolleyModalEngine"]');
      if(engineLabel) engineLabel.textContent = (currentEditType === 'storage') ? 'Bereich / Zone' : 'Engine Type';

      // Populate responsible persons datalist
      const dl = document.getElementById('responsiblePersonsList');
      dl.innerHTML = allEmployeeNames.map(n => `<option value="${n}">`).join('');

      if (name && dataSource[name]) {
          document.getElementById('trolleyModalTitle').textContent = name;
          document.getElementById('trolleyModalSubtitle').textContent = 'Details anzeigen';
          document.getElementById('trolleyModalOriginalName').value = name;
          nameInput.value = name;
          document.getElementById('trolleyModalEngine').value = dataSource[name].engineType || '';
          document.getElementById('trolleyModalResponsible').value = dataSource[name].responsible || '';

          currentLevelImages = Object.assign({}, dataSource[name].level_images || {});

          const imagesList = document.getElementById('trolleyImagesList');
          (dataSource[name].images || []).forEach(img => addTrolleyImageItem(imagesList, img.path, img.name, img.stored_name));
          document.getElementById('trolleyImagesEmpty').style.display = (dataSource[name].images || []).length ? 'none' : 'block';

          const items = [...(dataSource[name].items || [])];
          
          if (items.length === 0) {
              document.getElementById('trolleyEmptyState').classList.remove('hidden');
          } else {
              document.getElementById('trolleyEmptyState').classList.add('hidden');
              
              // SORTIERUNG: Ebene -> Box
              items.sort((a, b) => {
                  const levelA = String(a.level || '');
                  const levelB = String(b.level || '');
                  const levelCompare = levelA.localeCompare(levelB, undefined, { numeric: true, sensitivity: 'base' });
                  if (levelCompare !== 0) return levelCompare;
                  const boxA = String(a.box || '');
                  const boxB = String(b.box || '');
                  return boxA.localeCompare(boxB, undefined, { numeric: true, sensitivity: 'base' });
              });

              items.forEach(item => addTrolleyItemRow(item));
          }

          // Löschen-Button konfigurieren (Klonen um alte Listener zu entfernen)
          const delBtn = document.getElementById('deleteTrolleyBtn');
          const newDelBtn = delBtn.cloneNode(true);
          delBtn.parentNode.replaceChild(newDelBtn, delBtn);
          newDelBtn.onclick = () => deleteTrolley(name); // Nutzt deleteTrolley als generische Löschfunktion (siehe unten)

          // Formular initialisieren
          const today = new Date().toISOString().split('T')[0];
          const checkDateEl = document.getElementById('checkDate');
          if (checkDateEl) checkDateEl.value = today;
          const checkPrueferEl = document.getElementById('checkPruefer');
          if (checkPrueferEl) checkPrueferEl.value = CURRENT_USER_DISPLAY_NAME || '';
          const checkNotesEl = document.getElementById('checkNotes');
          if (checkNotesEl) checkNotesEl.value = '';
          currentlyEditingEntryId = null;
          const saveBtn = document.querySelector('#completenessCheckPanel button[onclick="saveCompletenessCheck()"]');
          if (saveBtn) saveBtn.textContent = 'Prüfung speichern';
          switchCompletenessTab('check');
          // Accordion eingeklappt starten
          const completenessBody = document.getElementById('completenessBody');
          if (completenessBody) completenessBody.classList.remove('hidden');
          const completenessChevron = document.getElementById('completenessChevron');
          if (completenessChevron) completenessChevron.style.transform = 'rotate(90deg)';
          // Completeness Log async laden (non-blocking)
          updateCompletenessStatusDisplay([]);
          loadAndRenderCompletenessLog(name, dataSource[name].items || []);

          toggleTrolleyEditMode(false); // Start im View-Modus
      } else {
          document.getElementById('trolleyModalTitle').textContent = `Neuer ${titlePrefix}`;
          document.getElementById('trolleyModalSubtitle').textContent = 'Erstellen';
          document.getElementById('trolleyModalOriginalName').value = '';
          document.getElementById('trolleyModalResponsible').value = '';
          document.getElementById('trolleyEmptyState').classList.add('hidden');
          currentLevelImages = {};
          currentCompletenessLog = [];
          updateCompletenessStatusDisplay([]);
          const newStatusEl = document.getElementById('trolleyCompletenessStatus');
          if (newStatusEl) newStatusEl.innerHTML = '<span class="text-slate-400 italic">Noch nie geprüft</span>';
          addTrolleyItemRow();
          toggleTrolleyEditMode(true); // Start im Edit-Modus
      }
      renderStorageGrid();
      openModal(trolleyModal);
  }

  function closeTrolleyModal() { closeModal(trolleyModal); }

  async function uploadTrolleyImage(inputEl) {
      const file = inputEl.files[0];
      if (!file) return;
      const formData = new FormData();
      formData.append('file', file);
      try {
          const res = await fetch('/api/hardware/upload', { method: 'POST', body: formData });
          if (!res.ok) throw new Error();
          const data = await res.json();
          const list = document.getElementById('trolleyImagesList');
          addTrolleyImageItem(list, data.path, data.filename, data.stored_name);
          document.getElementById('trolleyImagesEmpty').style.display = 'none';
          inputEl.value = '';
      } catch(e) {
          alert('Upload fehlgeschlagen.');
      }
  }

  function addTrolleyImageItem(listEl, path, name, storedName) {
      const div = document.createElement('div');
      div.className = 'trolley-img-item flex items-center justify-between p-2 bg-white border border-slate-200 rounded text-sm';
      div.dataset.path = path;
      div.dataset.name = name;
      div.dataset.stored = storedName;
      div.innerHTML = `
          <div class="flex items-center gap-2 cursor-pointer truncate" onclick="openLightbox('${path}', '${name}')">
              <img src="${path}" class="w-8 h-8 object-cover rounded border border-slate-100 flex-shrink-0">
              <span class="truncate text-slate-700" title="${name}">${name}</span>
          </div>
          <button type="button" class="trolley-img-delete-btn text-red-400 hover:text-red-600 ml-2 flex-shrink-0" onclick="this.closest('.trolley-img-item').remove(); document.getElementById('trolleyImagesEmpty').style.display = document.querySelectorAll('#trolleyImagesList .trolley-img-item').length ? 'none' : 'block';">
              <span class="material-icons-outlined text-base">close</span>
          </button>
      `;
      // Hide delete button in view mode
      if (!isTrolleyEditMode) div.querySelector('.trolley-img-delete-btn').style.visibility = 'hidden';
      listEl.appendChild(div);
  }

  function addTrolleyItem() { addTrolleyItemRow(null, true); }

  const LEVEL_COLOR_PALETTE = [
      { border: '#3b82f6', bg: 'rgba(59,130,246,0.07)', text: '#1d4ed8', bar: '#93c5fd' },
      { border: '#22c55e', bg: 'rgba(34,197,94,0.07)',  text: '#15803d', bar: '#86efac' },
      { border: '#f59e0b', bg: 'rgba(245,158,11,0.07)', text: '#b45309', bar: '#fcd34d' },
      { border: '#a855f7', bg: 'rgba(168,85,247,0.07)', text: '#7e22ce', bar: '#d8b4fe' },
      { border: '#ec4899', bg: 'rgba(236,72,153,0.07)', text: '#9d174d', bar: '#f9a8d4' },
      { border: '#14b8a6', bg: 'rgba(20,184,166,0.07)', text: '#0f766e', bar: '#5eead4' },
  ];
  const _levelColorCache = {};
  function getLevelStyle(levelValue) {
      if (levelValue === undefined || levelValue === null || levelValue === '') return null;
      const key = String(levelValue).trim();
      if (key === '') return null;
      if (_levelColorCache[key] !== undefined) return _levelColorCache[key];
      const num = parseFloat(key);
      let idx = !isNaN(num)
          ? (Math.floor(num) - 1 + LEVEL_COLOR_PALETTE.length) % LEVEL_COLOR_PALETTE.length
          : [...key].reduce((h, c) => (h + c.charCodeAt(0)) % LEVEL_COLOR_PALETTE.length, 0);
      _levelColorCache[key] = LEVEL_COLOR_PALETTE[Math.abs(idx)];
      return _levelColorCache[key];
  }

  function addTrolleyItemRow(data = null, prepend = false) {
      document.getElementById('trolleyEmptyState').classList.add('hidden');
      const container = document.getElementById('trolleyItemsContainer');
      const div = document.createElement('div');

      const baseClass = 'trolley-item-row grid grid-cols-12 gap-2 items-center p-2 rounded transition-colors';
      div.className = isTrolleyEditMode ? `${baseClass} border border-slate-100 hover:bg-slate-50` : baseClass;

      const id = data ? data.id : null; 
      const pn = data ? data.pn : '';
      const sn = data ? data.sn : ''; 
      const name = data ? data.name : ''; 
      const level = data ? data.level : '';
      const box = data && data.box ? data.box : '';
      const qty = data && data.qty ? data.qty : 1;
      
      const isLinked = !!id;
      const isLocked = isLinked && isTrolleyEditMode; 
      const lockClass = isLocked ? 'bg-slate-100 text-slate-500 cursor-not-allowed' : 'bg-white';

      const uniqueIdPN = 'list_pn_' + Math.random().toString(36).substr(2, 9);
      const uniqueIdSN = 'list_sn_' + Math.random().toString(36).substr(2, 9);

      let pnOptions = '';
      Object.keys(hardwareInventory).sort().forEach(p => { 
          const pName = hardwareInventory[p].name || '';
          pnOptions += `<option value="${p}">${p} - ${pName}</option>`; 
      });

      div.dataset.itemId = id || '';

      // Level-Farbgebung: Left-Border + dezenter Hintergrund
      const lvlStyle = getLevelStyle(level);
      if (lvlStyle) {
          div.style.borderLeft = `4px solid ${lvlStyle.border}`;
          div.style.backgroundColor = lvlStyle.bg;
          div.style.paddingLeft = '6px';
      }

      // Icon Logik für Freitext
      let nameFieldHtml = `
            <input type="text" class="w-full p-1.5 border border-slate-300 rounded text-sm trolley-name-input ${lockClass}" 
                   value="${name}" placeholder="Name / Beschreibung" ${isLocked ? 'readonly' : ''}>`;

      if (!isLinked) {
          // Freitext: Icon hinzufügen
          nameFieldHtml = `
            <div class="relative w-full">
                <input type="text" class="w-full p-1.5 pr-7 border border-slate-300 rounded text-sm trolley-name-input ${lockClass}" 
                       value="${name}" placeholder="Name / Beschreibung" ${isLocked ? 'readonly' : ''}>
                <span class="material-icons-outlined absolute right-1.5 top-1.5 text-slate-400 text-base pointer-events-none" title="Freitext / Manuell">edit_note</span>
            </div>`;
      }

      const checkedOutBy = data && data.checkedOutBy ? data.checkedOutBy : null;
      const checkedOutAt = data && data.checkedOutAt ? data.checkedOutAt : null;
      const checkedOutReason = data && data.checkedOutReason ? data.checkedOutReason : '';
      let checkoutSectionHtml = '';
      if (id) {
          if (checkedOutBy) {
              const elapsed = timeAgo(checkedOutAt);
              const shortName = (checkedOutBy.split(' ')[0] || '').substring(0, 10);
              const esc = s => (s || '').replace(/"/g, '&quot;');
              const checkinTitle = esc('Check-in: ' + checkedOutBy + ' (' + elapsed + ')' + (checkedOutReason ? ' · ' + checkedOutReason : ''));
              checkoutSectionHtml = `<div class="trolley-checkout-section flex items-center gap-0.5 flex-shrink-0"><span class="text-xs text-orange-500 leading-tight truncate max-w-[56px]" title="${esc(checkedOutBy)}">${shortName}</span><button type="button" class="text-orange-500 hover:text-green-600 p-1 rounded-full hover:bg-green-50 flex-shrink-0" title="${checkinTitle}" onclick="checkInItem(document.getElementById('trolleyModalOriginalName').value,this.closest('.trolley-item-row').dataset.itemId)"><span class="material-icons-outlined text-base">login</span></button></div>`;
          } else {
              checkoutSectionHtml = `<div class="trolley-checkout-section flex items-center flex-shrink-0"><button type="button" class="text-slate-300 hover:text-blue-600 p-1 rounded-full hover:bg-blue-50" title="Check-out" onclick="checkOutItem(document.getElementById('trolleyModalOriginalName').value,this.closest('.trolley-item-row').dataset.itemId)"><span class="material-icons-outlined text-base">logout</span></button></div>`;
          }
      }

      div.innerHTML = `
        <!-- P/N (2 Spalten) -->
        <div class="col-span-2 relative">
            <input type="text" list="${uniqueIdPN}" class="w-full p-1.5 border border-slate-300 rounded text-sm trolley-pn-input ${lockClass}" 
                   value="${pn}" placeholder="P/N..." onchange="updateSnOptions(this)" ${isLocked ? 'readonly' : ''}>
            <datalist id="${uniqueIdPN}">${pnOptions}</datalist>
        </div>

        <!-- S/N (2 Spalten) -->
        <div class="col-span-2 relative">
            <input type="text" list="${uniqueIdSN}" class="w-full p-1.5 border border-slate-300 rounded text-sm trolley-sn-input ${lockClass}" 
                   value="${sn}" placeholder="S/N..." onchange="onSnSelected(this)" ${!pn || isLocked ? 'disabled' : ''}>
            <datalist id="${uniqueIdSN}"></datalist>
        </div>

        <!-- Name (3 Spalten) - MIT ICON -->
        <div class="col-span-3">
            ${nameFieldHtml}
        </div>

        <!-- Menge (1 Spalte) -->
        <div class="col-span-1">
             <input type="number" min="1" class="w-full p-1.5 border border-slate-300 rounded text-sm text-center trolley-qty-input ${lockClass}" 
                    value="${qty}" placeholder="#" ${isLocked ? 'readonly' : ''}>
        </div>

        <!-- Position (3 Spalten) -->
        <div class="col-span-3 flex gap-1">
            <input type="text" class="w-1/2 p-1.5 border rounded text-sm trolley-level-input ${!isTrolleyEditMode ? 'bg-slate-50 border-transparent' : 'border-slate-300'}" placeholder="Lvl" value="${level}" ${!isTrolleyEditMode ? 'readonly' : ''}>
            <input type="text" class="w-1/2 p-1.5 border rounded text-sm trolley-box-input ${!isTrolleyEditMode ? 'bg-slate-50 border-transparent' : 'border-slate-300'}" placeholder="Box" value="${box}" ${!isTrolleyEditMode ? 'readonly' : ''}>
        </div>

        <!-- Aktion: Delete (Edit-Mode) / Checkout-Status (View-Mode) -->
        <div class="col-span-1 flex justify-end items-center">
            <button type="button" class="trolley-item-delete-btn text-red-400 hover:text-red-600 p-1 rounded-full hover:bg-red-50 transition-colors" onclick="this.closest('.trolley-item-row').remove()">
                <span class="material-icons-outlined text-lg">delete</span>
            </button>
            ${checkoutSectionHtml}
        </div>
      `;
      const chkSection = div.querySelector('.trolley-checkout-section');
      if (chkSection) chkSection.style.display = isTrolleyEditMode ? 'none' : 'flex';
      if (prepend && container.firstChild) {
            container.insertBefore(div, container.firstChild);
            // Optional: Fokus auf das erste Eingabefeld setzen
            if (isTrolleyEditMode) {
                 const firstInput = div.querySelector('input:not([readonly]):not([disabled])');
                 if (firstInput) setTimeout(() => firstInput.focus(), 50);
            }
        } else {
            container.appendChild(div);
        }

      if (pn && !isLocked) {
          const snInput = div.querySelector('.trolley-sn-input');
          snInput.disabled = false;
          updateSnDropdownContent(snInput, pn);
      }
      
      if (!isTrolleyEditMode) {
          const inputs = div.querySelectorAll('input');
          inputs.forEach(i => { 
              i.readOnly = true; 
              // Spezielle Behandlung für das Icon-Feld: Nicht den Wrapper, sondern das Input stylen
              if (!i.parentElement.classList.contains('relative')) {
                  i.classList.add('bg-slate-50', 'border-transparent', 'text-slate-600'); 
                  i.classList.remove('bg-white', 'border-slate-300', 'bg-slate-100');
              } else {
                  // Wenn es im Wrapper ist (unser Name-Feld)
                  i.classList.add('bg-slate-50', 'border-transparent', 'text-slate-600'); 
                  i.classList.remove('bg-white', 'border-slate-300', 'bg-slate-100');
              }
          });
          div.querySelector('.trolley-item-delete-btn').style.visibility = 'hidden';
      }
  }

  function jumpToCompleteness() {
      const body = document.getElementById('completenessBody');
      const chevron = document.getElementById('completenessChevron');
      const section = document.getElementById('trolleyCompletenessSection');
      if (body) body.classList.remove('hidden');
      if (chevron) chevron.style.transform = 'rotate(90deg)';
      if (section) section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  }

  async function loadCompletenessLog(containerName) {
      try {
          const res = await fetch(`/api/completeness_log?container=${encodeURIComponent(containerName)}`);
          if (!res.ok) return [];
          return await res.json();
      } catch(e) {
          console.error('Fehler beim Laden des Completeness Logs:', e);
          return [];
      }
  }

  function updateCompletenessStatusDisplay(log) {
      const statusEl = document.getElementById('trolleyCompletenessStatus');
      if (!statusEl) return;
      if (log && log.length > 0) {
          const latest = log[0]; // bereits absteigend sortiert
          const elapsed = timeAgo(latest.timestamp);
          const dateStr = latest.date ? latest.date.split('-').reverse().join('.') : '';
          const shiftLabel = latest.shift === 'F' ? 'Früh' : 'Spät';
          const col = latest.status === 'vollständig' ? 'text-green-600' : 'text-orange-500';
          statusEl.innerHTML = `<span class="${col} font-medium">${latest.status === 'vollständig' ? '✓' : '⚠'} ${elapsed}</span> · ${latest.checkedBy} · ${shiftLabel} (${dateStr})`;
      } else {
          statusEl.innerHTML = '<span class="text-slate-400 italic">Noch nie geprüft</span>';
      }
  }

  async function loadAndRenderCompletenessLog(containerName, items) {
      currentCompletenessLog = await loadCompletenessLog(containerName);
      updateCompletenessStatusDisplay(currentCompletenessLog);
      renderCompletenessHistory(currentCompletenessLog);
      setCheckStatus('vollständig');
      populateMissingItemsList(items || []);
  }

  function toggleCompletenessAccordion() {
      const body = document.getElementById('completenessBody');
      const chevron = document.getElementById('completenessChevron');
      if (!body) return;
      const isOpen = !body.classList.contains('hidden');
      body.classList.toggle('hidden', isOpen);
      if (chevron) chevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(90deg)';
  }

  function toggleStorageGridAccordion() {
      const body = document.getElementById('storageGridBody');
      const chevron = document.getElementById('storageGridChevron');
      if (!body) return;
      const isOpen = !body.classList.contains('hidden');
      body.classList.toggle('hidden', isOpen);
      if (chevron) chevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(90deg)';
  }

  function renderStorageGrid() {
      const container = document.getElementById('storageGrid2D');
      const emptyEl = document.getElementById('storageGridEmpty');
      if (!container || !emptyEl) return;

      const name = document.getElementById('trolleyModalOriginalName').value;
      if (!name) { container.innerHTML = ''; emptyEl.classList.remove('hidden'); return; }

      const dataSource = currentEditType === 'storage' ? storageUnits : trolleys;
      const items = (dataSource[name] && dataSource[name].items) || [];
      const positioned = items.filter(i => i.level !== undefined && i.level !== '');

      if (positioned.length === 0) {
          container.innerHTML = '';
          emptyEl.classList.remove('hidden');
          return;
      }
      emptyEl.classList.add('hidden');

      // Ebene → Items sammeln
      const levelMap = {};
      positioned.forEach(item => {
          const lvl = String(item.level);
          if (!levelMap[lvl]) levelMap[lvl] = [];
          levelMap[lvl].push(item);
      });

      // E1 oben → aufsteigend sortieren
      const levels = Object.keys(levelMap).sort((a, b) => a.localeCompare(b, undefined, {numeric: true}));

      const esc = s => String(s || '').replace(/"/g, '&quot;').replace(/'/g, '&#39;');

      let html = '<div class="space-y-1">';
      levels.forEach(lvl => {
          const lvlItems = levelMap[lvl];
          const total = lvlItems.length;
          const checkedOut = lvlItems.filter(i => i.checkedOutBy).length;
          const present = total - checkedOut;
          const pctPresent = total > 0 ? (present / total * 100).toFixed(1) : 0;
          const pctOut    = total > 0 ? (checkedOut / total * 100).toFixed(1) : 0;

          const presentNames = lvlItems.filter(i => !i.checkedOutBy).map(i => i.name || i.pn).join(', ');
          const outNames     = lvlItems.filter(i =>  i.checkedOutBy).map(i => i.name || i.pn).join(', ');
          let tooltipParts = [];
          if (presentNames) tooltipParts.push(`Vorhanden: ${presentNames}`);
          if (outNames)     tooltipParts.push(`Ausgecheckt: ${outNames}`);
          const tooltip = esc(tooltipParts.join(' | '));

          const lvlColor = getLevelStyle(lvl);
          const barColor = lvlColor ? lvlColor.bar : '#93c5fd';
          const labelColor = lvlColor ? lvlColor.text : '#64748b';
          const borderColor = lvlColor ? lvlColor.border : '#94a3b8';
          const bgColor = lvlColor ? lvlColor.bg : 'transparent';
          const dotStyle = `display:inline-block;width:7px;height:7px;border-radius:50%;background:${borderColor};margin-right:4px;flex-shrink:0;`;

          const lvlImg = currentLevelImages[lvl];
          const hasPhoto = !!(lvlImg && lvlImg.path);
          const photoBadge = hasPhoto
              ? `<span class="text-xs font-medium ml-1" style="color:${labelColor}">📷</span>`
              : `<span class="text-xs text-slate-300 ml-1">📷</span>`;

          html += `
          <div class="rounded-md overflow-hidden" style="border:1px solid ${borderColor}30">
              <!-- Balken-Zeile, klickbar -->
              <div class="flex items-center gap-2 px-2 py-1.5 cursor-pointer hover:opacity-80 transition-opacity"
                   style="background:${bgColor}"
                   title="${tooltip}"
                   onclick="toggleLevelRow('${esc(lvl)}')">
                  <span class="material-icons-outlined text-slate-300 transition-transform duration-200 text-sm flex-shrink-0"
                        id="lvlChevron_${esc(lvl)}" style="transform:rotate(0deg)">chevron_right</span>
                  <span class="w-6 text-xs font-semibold flex-shrink-0 flex items-center" style="color:${labelColor}">
                      <span style="${dotStyle}"></span>E${esc(lvl)}
                  </span>
                  <div class="flex-grow h-2 rounded-full bg-slate-100 overflow-hidden flex">
                      <div class="h-full transition-all duration-300" style="width:${pctPresent}%;background:${barColor}"></div>
                      <div class="h-full bg-orange-400 transition-all duration-300" style="width:${pctOut}%"></div>
                  </div>
                  ${photoBadge}
              </div>
              <!-- Aufklappbarer Foto-Bereich -->
              <div id="lvlPanel_${esc(lvl)}" class="hidden border-t px-2 py-2" style="border-color:${borderColor}30; background:${bgColor}">
                  ${hasPhoto ? `
                  <div class="relative group mb-1.5">
                      <img src="${esc(lvlImg.path)}" alt="Ebene ${esc(lvl)}"
                           class="w-full rounded object-cover cursor-pointer"
                           style="max-height:120px"
                           onclick="openLightbox('${esc(lvlImg.path)}','${esc(lvlImg.name)}')">
                      <div id="lvlImgOverlay_${esc(lvl)}" class="hidden absolute inset-0 bg-black/40 rounded flex items-center justify-center gap-2">
                          <label class="cursor-pointer bg-white/90 text-slate-700 rounded-full p-1.5 hover:bg-white transition-colors" title="Ersetzen">
                              <span class="material-icons-outlined text-base">upload</span>
                              <input type="file" accept="image/*" class="hidden" onchange="uploadLevelImage('${esc(lvl)}',this)">
                          </label>
                          <button type="button" onclick="deleteLevelImage('${esc(lvl)}')"
                                  class="bg-white/90 text-red-500 rounded-full p-1.5 hover:bg-white transition-colors" title="Löschen">
                              <span class="material-icons-outlined text-base">delete</span>
                          </button>
                      </div>
                  </div>
                  ` : `
                  <div class="flex flex-col items-center justify-center py-3 rounded border border-dashed border-slate-200 mb-1.5" style="background:white">
                      <span class="material-icons-outlined text-slate-300 text-2xl mb-1">add_photo_alternate</span>
                      <span class="text-xs text-slate-400">Kein Referenzfoto</span>
                  </div>
                  `}
                  <div id="lvlUploadArea_${esc(lvl)}" class="${isTrolleyEditMode ? '' : 'hidden'}">
                      <label class="flex items-center justify-center gap-1 w-full py-1.5 text-xs font-medium rounded cursor-pointer transition-colors hover:opacity-80"
                             style="color:${labelColor};background:${bgColor};border:1px solid ${borderColor}50">
                          <span class="material-icons-outlined text-base">add_photo_alternate</span>
                          ${hasPhoto ? 'Foto ersetzen' : 'Foto hinzufügen'}
                          <input type="file" accept="image/*" class="hidden" onchange="uploadLevelImage('${esc(lvl)}',this)">
                      </label>
                  </div>
              </div>
          </div>`;
      });
      html += '</div>';

      // Legende
      html += `<div class="flex gap-4 mt-3 pt-2 border-t border-slate-100">
          <div class="flex items-center gap-1.5"><div class="w-3 h-2 rounded-full flex-shrink-0" style="background:${LEVEL_COLOR_PALETTE[0].bar}"></div><span class="text-xs text-slate-400">Vorhanden</span></div>
          <div class="flex items-center gap-1.5"><div class="w-3 h-2 rounded-full bg-orange-400 flex-shrink-0"></div><span class="text-xs text-slate-400">Ausgecheckt</span></div>
          <div class="flex items-center gap-1.5"><div class="w-3 h-2 rounded-full bg-slate-100 border border-slate-200 flex-shrink-0"></div><span class="text-xs text-slate-400">Leer</span></div>
      </div>`;

      container.innerHTML = html;

      // Edit-Modus: Hover-Overlay auf vorhandenen Fotos aktivieren
      if (isTrolleyEditMode) {
          levels.forEach(lvl => {
              const overlay = document.getElementById(`lvlImgOverlay_${lvl}`);
              const img = overlay ? overlay.previousElementSibling : null;
              if (overlay && img) {
                  img.addEventListener('mouseenter', () => overlay.classList.remove('hidden'));
                  img.addEventListener('mouseleave', () => overlay.classList.add('hidden'));
                  overlay.addEventListener('mouseenter', () => overlay.classList.remove('hidden'));
                  overlay.addEventListener('mouseleave', () => overlay.classList.add('hidden'));
              }
          });
      }
  }

  function toggleLevelRow(lvl) {
      const panel = document.getElementById('lvlPanel_' + lvl);
      const chevron = document.getElementById('lvlChevron_' + lvl);
      if (!panel) return;
      const isOpen = !panel.classList.contains('hidden');
      panel.classList.toggle('hidden', isOpen);
      if (chevron) chevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(90deg)';
  }

  async function uploadLevelImage(lvl, inputEl) {
      const file = inputEl.files[0];
      if (!file) return;
      const formData = new FormData();
      formData.append('file', file);
      try {
          const res = await fetch('/api/hardware/upload', { method: 'POST', body: formData });
          if (!res.ok) throw new Error('Upload fehlgeschlagen');
          const data = await res.json();
          currentLevelImages[lvl] = { path: data.path, name: data.filename, stored_name: data.stored_name };
          renderStorageGrid();
          // Panel nach Re-Render wieder öffnen
          const panel = document.getElementById('lvlPanel_' + lvl);
          const chevron = document.getElementById('lvlChevron_' + lvl);
          if (panel) panel.classList.remove('hidden');
          if (chevron) chevron.style.transform = 'rotate(90deg)';
      } catch(err) {
          alert('Foto-Upload fehlgeschlagen: ' + err.message);
      }
      inputEl.value = '';
  }

  function deleteLevelImage(lvl) {
      delete currentLevelImages[lvl];
      renderStorageGrid();
      const panel = document.getElementById('lvlPanel_' + lvl);
      const chevron = document.getElementById('lvlChevron_' + lvl);
      if (panel) panel.classList.remove('hidden');
      if (chevron) chevron.style.transform = 'rotate(90deg)';
  }

  function switchCompletenessTab(tab) {
      const checkPanel = document.getElementById('completenessCheckPanel');
      const historyPanel = document.getElementById('completenessHistoryPanel');
      const tabCheck = document.getElementById('completenessTabCheck');
      const tabHistory = document.getElementById('completenessTabHistory');
      if (!checkPanel || !historyPanel) return;
      if (tab === 'check') {
          checkPanel.classList.remove('hidden');
          historyPanel.classList.add('hidden');
          tabCheck.classList.add('border-blue-500', 'text-blue-600');
          tabCheck.classList.remove('border-transparent', 'text-slate-500');
          tabHistory.classList.remove('border-blue-500', 'text-blue-600');
          tabHistory.classList.add('border-transparent', 'text-slate-500');
      } else {
          historyPanel.classList.remove('hidden');
          checkPanel.classList.add('hidden');
          tabHistory.classList.add('border-blue-500', 'text-blue-600');
          tabHistory.classList.remove('border-transparent', 'text-slate-500');
          tabCheck.classList.remove('border-blue-500', 'text-blue-600');
          tabCheck.classList.add('border-transparent', 'text-slate-500');
      }
  }

  function setCheckStatus(status) {
      const btnVoll = document.getElementById('btnStatusVoll');
      const btnUnvoll = document.getElementById('btnStatusUnvoll');
      const missingSection = document.getElementById('missingItemsSection');
      if (!btnVoll || !btnUnvoll || !missingSection) return;
      if (status === 'vollständig') {
          btnVoll.className = 'flex-1 py-2 rounded-lg text-sm font-medium bg-green-100 text-green-700 border-2 border-green-400 transition-colors';
          btnUnvoll.className = 'flex-1 py-2 rounded-lg text-sm font-medium bg-white text-slate-500 border border-slate-300 hover:bg-slate-50 transition-colors';
          missingSection.classList.add('hidden');
      } else {
          btnUnvoll.className = 'flex-1 py-2 rounded-lg text-sm font-medium bg-orange-100 text-orange-700 border-2 border-orange-400 transition-colors';
          btnVoll.className = 'flex-1 py-2 rounded-lg text-sm font-medium bg-white text-slate-500 border border-slate-300 hover:bg-slate-50 transition-colors';
          missingSection.classList.remove('hidden');
      }
  }

  function populateMissingItemsList(items) {
      const container = document.getElementById('missingItemsList');
      if (!container) return;
      container.innerHTML = '';
      if (!items || items.length === 0) {
          container.innerHTML = '<p class="text-xs text-slate-400 italic p-2">Keine BOM-Einträge vorhanden.</p>';
          return;
      }
      items.forEach((item, idx) => {
          const label = [item.pn, item.sn ? '/ ' + item.sn : '', item.name ? '— ' + item.name : ''].filter(Boolean).join(' ');
          const div = document.createElement('div');
          div.className = 'flex items-center gap-2 p-1.5 rounded hover:bg-slate-100 cursor-pointer';
          div.innerHTML = `<input type="checkbox" id="missingItem_${idx}" class="w-4 h-4 text-orange-500 rounded border-slate-300 focus:ring-orange-500" data-item-name="${label.replace(/"/g, '&quot;')}">
                           <label for="missingItem_${idx}" class="text-xs text-slate-700 cursor-pointer flex-1">${label}</label>`;
          container.appendChild(div);
      });
  }

  async function saveCompletenessCheck() {
      const name = document.getElementById('trolleyModalOriginalName').value;
      if (!name) return;
      const date = document.getElementById('checkDate').value;
      const shift = document.getElementById('checkShift').value;
      const checkedBy = (document.getElementById('checkPruefer').value || '').trim();
      const notes = (document.getElementById('checkNotes').value || '').trim();
      const btnUnvoll = document.getElementById('btnStatusUnvoll');
      const isUnvoll = btnUnvoll && btnUnvoll.classList.contains('border-orange-400');
      const status = isUnvoll ? 'unvollständig' : 'vollständig';
      if (!date) { alert('Bitte Datum angeben.'); return; }
      if (!checkedBy) { alert('Bitte Prüfer angeben.'); return; }
      const missingItems = [];
      if (isUnvoll) {
          document.querySelectorAll('#missingItemsList input[type="checkbox"]:checked').forEach(cb => {
              missingItems.push(cb.dataset.itemName);
          });
      }
      const containerType = currentEditType === 'storage' ? 'storage' : 'trolley';
      const entry = {
          id: currentlyEditingEntryId || ('chk_' + Math.random().toString(36).substr(2, 9)),
          container: name,
          container_type: containerType,
          date, shift, checkedBy, status, missingItems, notes,
          timestamp: currentlyEditingEntryId
              ? (currentCompletenessLog.find(e => e.id === currentlyEditingEntryId) || {}).timestamp || new Date().toISOString()
              : new Date().toISOString()
      };
      try {
          const res = await fetch('/api/completeness_log', {
              method: 'POST',
              headers: {'Content-Type': 'application/json'},
              body: JSON.stringify(entry)
          });
          if (!res.ok) { alert('Fehler beim Speichern des Prüfeintrags.'); return; }
      } catch(e) {
          alert('Netzwerkfehler.'); return;
      }
      currentlyEditingEntryId = null;
      // Formular zurücksetzen
      document.getElementById('checkNotes').value = '';
      setCheckStatus('vollständig');
      // Log neu laden und UI aktualisieren
      const db = currentEditType === 'storage' ? storageUnits : trolleys;
      if (db[name]) { db[name].lastCheckedBy = checkedBy; db[name].lastCheckedAt = entry.timestamp; }
      await loadAndRenderCompletenessLog(name, (db[name] && db[name].items) || []);
      // Button-Flash: kurz grün, dann zum Verlauf-Tab wechseln
      const saveBtn = document.querySelector('#completenessCheckPanel button[onclick="saveCompletenessCheck()"]');
      if (saveBtn) {
          saveBtn.textContent = '✓ Gespeichert!';
          saveBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
          saveBtn.classList.add('bg-green-600', 'hover:bg-green-700');
          setTimeout(() => {
              saveBtn.textContent = 'Prüfung speichern';
              saveBtn.classList.remove('bg-green-600', 'hover:bg-green-700');
              saveBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
              switchCompletenessTab('history');
          }, 1500);
      } else {
          switchCompletenessTab('history');
      }
  }

  function renderCompletenessHistory(log) {
      const container = document.getElementById('completenessHistoryList');
      if (!container) return;
      if (!log || log.length === 0) {
          container.innerHTML = '<p class="text-sm text-slate-400 italic text-center py-4">Noch keine Prüfeinträge vorhanden.</p>';
          return;
      }
      const sorted = [...log].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
      container.innerHTML = sorted.map(entry => {
          const isVoll = entry.status === 'vollständig';
          const shiftLabel = entry.shift === 'F' ? 'Früh' : 'Spät';
          const dateStr = entry.date ? entry.date.split('-').reverse().join('.') : '';
          const statusBadge = isVoll
              ? '<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">✓ Vollständig</span>'
              : '<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-800">⚠ Unvollständig</span>';
          const missingHtml = (!isVoll && entry.missingItems && entry.missingItems.length > 0)
              ? `<div class="mt-1 text-xs text-orange-700">Fehlend: ${entry.missingItems.join(', ')}</div>` : '';
          const notesHtml = entry.notes
              ? `<div class="mt-0.5 text-xs text-slate-500 italic">${entry.notes}</div>` : '';
          const adminBtns = isCurrentUserAdmin
              ? `<button type="button" onclick="editCompletenessEntry('${entry.id}')"
                         class="flex-shrink-0 text-slate-300 hover:text-blue-500 transition-colors" title="Eintrag bearbeiten">
                     <span class="material-icons-outlined text-base leading-none">edit</span>
                 </button>
                 <button type="button" onclick="deleteCompletenessEntry('${entry.id}')"
                         class="flex-shrink-0 text-slate-300 hover:text-red-500 transition-colors" title="Eintrag löschen">
                     <span class="material-icons-outlined text-base leading-none">delete</span>
                 </button>` : '';
          return `<div class="p-2.5 rounded-lg border ${isVoll ? 'border-green-200 bg-green-50' : 'border-orange-200 bg-orange-50'}">
              <div class="flex items-center justify-between gap-2 flex-wrap">
                  <div class="flex items-center gap-2 flex-1 min-w-0">
                      <span class="text-xs font-semibold text-slate-700">${dateStr}</span>
                      <span class="text-xs text-slate-500">${shiftLabel}schicht</span>
                      <span class="text-xs text-slate-600">&middot; ${entry.checkedBy}</span>
                  </div>
                  <div class="flex items-center gap-1 flex-shrink-0">
                      ${statusBadge}${adminBtns}
                  </div>
              </div>
              ${missingHtml}${notesHtml}
          </div>`;
      }).join('');
  }

  async function deleteCompletenessEntry(entryId) {
      if (!confirm('Diesen Prüfeintrag wirklich löschen?')) return;
      const name = document.getElementById('trolleyModalOriginalName').value;
      if (!name) return;
      try {
          const res = await fetch(`/api/completeness_log/${encodeURIComponent(entryId)}`, { method: 'DELETE' });
          if (!res.ok) { alert('Fehler beim Löschen.'); return; }
      } catch(e) {
          alert('Netzwerkfehler.'); return;
      }
      const db = currentEditType === 'storage' ? storageUnits : trolleys;
      await loadAndRenderCompletenessLog(name, (db[name] && db[name].items) || []);
  }

  function editCompletenessEntry(entryId) {
      const entry = currentCompletenessLog.find(e => e.id === entryId);
      if (!entry) return;
      const checkDateEl = document.getElementById('checkDate');
      if (checkDateEl) checkDateEl.value = entry.date || '';
      const checkShiftEl = document.getElementById('checkShift');
      if (checkShiftEl) checkShiftEl.value = entry.shift || 'F';
      const checkPrueferEl = document.getElementById('checkPruefer');
      if (checkPrueferEl) checkPrueferEl.value = entry.checkedBy || '';
      const checkNotesEl = document.getElementById('checkNotes');
      if (checkNotesEl) checkNotesEl.value = entry.notes || '';
      setCheckStatus(entry.status === 'unvollständig' ? 'unvollständig' : 'vollständig');
      if (entry.status === 'unvollständig' && entry.missingItems && entry.missingItems.length > 0) {
          document.querySelectorAll('#missingItemsList input[type="checkbox"]').forEach(cb => {
              cb.checked = entry.missingItems.includes(cb.dataset.itemName);
          });
      }
      currentlyEditingEntryId = entryId;
      const saveBtn = document.querySelector('#completenessCheckPanel button[onclick="saveCompletenessCheck()"]');
      if (saveBtn) saveBtn.textContent = 'Eintrag aktualisieren';
      const body = document.getElementById('completenessBody');
      const chevron = document.getElementById('completenessChevron');
      if (body) body.classList.remove('hidden');
      if (chevron) chevron.style.transform = 'rotate(90deg)';
      switchCompletenessTab('check');
  }

  function timeAgo(isoStr) {
      if (!isoStr) return '';
      const diff = Math.round((Date.now() - new Date(isoStr).getTime()) / 1000);
      if (diff < 60) return 'gerade';
      if (diff < 3600) return Math.floor(diff / 60) + ' Min.';
      if (diff < 86400) return Math.floor(diff / 3600) + ' Std.';
      return Math.floor(diff / 86400) + 'd';
  }

  function checkOutItem(containerName, itemId) {
      const db = currentEditType === 'storage' ? storageUnits : trolleys;
      if (!db[containerName]) return;
      const item = (db[containerName].items || []).find(i => i.id === itemId);
      if (!item) return;
      pendingCheckout = { containerName, itemId };
      const label = [item.pn, item.sn ? '/ ' + item.sn : '', item.name ? '— ' + item.name : ''].filter(Boolean).join(' ');
      document.getElementById('checkoutReasonItemLabel').textContent = label;
      document.getElementById('checkoutReasonInput').value = '';
      openModal(document.getElementById('checkoutReasonModal'));
      setTimeout(() => document.getElementById('checkoutReasonInput').focus(), 80);
  }

  function closeCheckoutReasonModal() {
      closeModal(document.getElementById('checkoutReasonModal'));
      pendingCheckout = null;
  }

  async function confirmCheckOut() {
      if (!pendingCheckout) return;
      const { containerName, itemId } = pendingCheckout;
      const reason = document.getElementById('checkoutReasonInput').value.trim();
      const db = currentEditType === 'storage' ? storageUnits : trolleys;
      if (!db[containerName]) return;
      const item = (db[containerName].items || []).find(i => i.id === itemId);
      if (!item) return;
      item.checkedOutBy = CURRENT_USER_DISPLAY_NAME;
      item.checkedOutAt = new Date().toISOString();
      if (reason) item.checkedOutReason = reason; else delete item.checkedOutReason;
      closeCheckoutReasonModal();
      try {
          await saveContainer(containerName, db[containerName]);
      } catch(err) {
          alert('Fehler beim Check-out: ' + err.message);
      }
      openTrolleyModal(containerName);
  }

  function checkInItem(containerName, itemId) {
      const db = currentEditType === 'storage' ? storageUnits : trolleys;
      if (!db[containerName]) return;
      const item = (db[containerName].items || []).find(i => i.id === itemId);
      if (!item) return;
      pendingCheckin = { containerName, itemId };
      const label = [item.pn, item.sn ? '/ ' + item.sn : '', item.name ? '— ' + item.name : ''].filter(Boolean).join(' ');
      document.getElementById('checkInItemLabel').textContent = label;
      const elapsed = timeAgo(item.checkedOutAt);
      const reasonText = item.checkedOutReason ? ' · ' + item.checkedOutReason : '';
      document.getElementById('checkInStatusInfo').textContent = 'Ausgebucht von ' + item.checkedOutBy + ' (' + elapsed + ')' + reasonText;
      openModal(document.getElementById('checkInConfirmModal'));
  }

  function closeCheckInModal() {
      closeModal(document.getElementById('checkInConfirmModal'));
      pendingCheckin = null;
  }

  async function confirmCheckIn() {
      if (!pendingCheckin) return;
      const { containerName, itemId } = pendingCheckin;
      const db = currentEditType === 'storage' ? storageUnits : trolleys;
      if (!db[containerName]) return;
      const item = (db[containerName].items || []).find(i => i.id === itemId);
      if (!item) return;
      delete item.checkedOutBy;
      delete item.checkedOutAt;
      delete item.checkedOutReason;
      closeCheckInModal();
      try {
          await saveContainer(containerName, db[containerName]);
      } catch(err) {
          alert('Fehler beim Check-in: ' + err.message);
      }
      openTrolleyModal(containerName);
  }

  function filterTrolleyItems(term) {
      const t = (term || '').toLowerCase();
      document.querySelectorAll('#trolleyItemsContainer .trolley-item-row').forEach(row => {
          if (!t) { row.style.display = ''; return; }
          const pnEl = row.querySelector('.trolley-pn-input');
          const snEl = row.querySelector('.trolley-sn-input');
          const nmEl = row.querySelector('.trolley-name-input');
          const pn = pnEl ? pnEl.value.toLowerCase() : '';
          const sn = snEl ? snEl.value.toLowerCase() : '';
          const nm = nmEl ? nmEl.value.toLowerCase() : '';
          row.style.display = (pn.includes(t) || sn.includes(t) || nm.includes(t)) ? '' : 'none';
      });
  }

    function onSnSelected(input) {
        const sn = input.value.trim();
        const row = input.closest('.trolley-item-row');
        const pn = row.querySelector('.trolley-pn-input').value;
        
        // Suche im Inventar nach diesem Item
        if (hardwareInventory[pn]) {
            const serialEntry = hardwareInventory[pn].serials.find(s => s.sn === sn);
            
            if (serialEntry && serialEntry.id) {
                // TREFFER: Verknüpftes Item!
                row.dataset.itemId = serialEntry.id;
                
                // Name automatisch setzen
                const nameInput = row.querySelector('.trolley-name-input');
                nameInput.value = hardwareInventory[pn].name || '';
                
                // Felder sperren (Visuelles Feedback)
                const fieldsToLock = [input, row.querySelector('.trolley-pn-input'), nameInput, row.querySelector('.trolley-qty-input')];
                fieldsToLock.forEach(f => {
                    f.readOnly = true;
                    f.classList.add('bg-slate-100', 'text-slate-500', 'cursor-not-allowed');
                    f.classList.remove('bg-white');
                });
                
                // Menge auf 1 setzen
                row.querySelector('.trolley-qty-input').value = 1;
            }
        }
    }
function updateSnOptions(pnInput) {
      const row = pnInput.closest('.trolley-item-row');
      const snInput = row.querySelector('.trolley-sn-input');
      const pn = pnInput.value.trim();

      if (!pn) {
          snInput.value = '';
          snInput.disabled = true; // Deaktivieren wenn P/N leer
          return;
      }
      snInput.disabled = false; 
      
      if (hardwareInventory[pn]) {
          updateSnDropdownContent(snInput, pn);
      } else {
          const datalistId = snInput.getAttribute('list');
          document.getElementById(datalistId).innerHTML = '';
      }
  }

  function updateSnDropdownContent(snInput, pn) {
      const datalistId = snInput.getAttribute('list');
      const datalist = document.getElementById(datalistId);
      datalist.innerHTML = ''; // Reset

      if (!pn || !hardwareInventory[pn]) return;

      const partData = hardwareInventory[pn];
      partData.serials.forEach(s => {
          // Zeige alle verfügbaren SNs an (außer Decommissioned)
          if (s.status !== 'Decommissioned') {
               let label = s.location ? `${s.location}` : 'Lager';
               const opt = document.createElement('option');
               opt.value = s.sn;
               opt.textContent = `Standort: ${label}`; // Wird im Browser als Zusatzinfo angezeigt
               datalist.appendChild(opt);
          }
      });
  }

  function assignInventoryToTrolley(trolleyName, items) {
      Object.values(hardwareInventory).forEach(part => {
          part.serials.forEach(s => { if (s.location === trolleyName) s.location = ""; });
      });

      items.forEach(item => {
          const partData = hardwareInventory[item.pn];
          if (!partData) return;
          const specificSerial = partData.serials.find(s => s.sn === item.sn);
          if (specificSerial) specificSerial.location = trolleyName;
      });
  }

  // Granularer Save für einen einzelnen Container (Rüstwagen/Lagerort).
  // Schreibt nur diesen einen Eintrag auf dem Server und gibt den neuen version_hash zurück.
  async function saveContainer(containerName, containerData) {
      const containerType = (currentEditType === 'storage') ? 'storage_units' : 'trolleys';
      const res = await fetch(`/api/test_equipment/container/${containerType}/${encodeURIComponent(containerName)}`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ container_data: containerData })
      });
      if (!res.ok) {
          const err = await res.json().catch(() => ({}));
          throw new Error(err.error || 'Speichern fehlgeschlagen');
      }
      const result = await res.json();
      if (result.version_hash) dataVersionHash = result.version_hash;
      return result;
  }

  // Diese Funktion löscht sowohl Rüstwagen als auch Lagerorte
  async function deleteTrolley(name) {
      const label = (currentEditType === 'storage') ? 'Lagerort' : 'Rüstwagen';
      if(!confirm(`${label} "${name}" wirklich löschen?`)) return;

      const containerType = (currentEditType === 'storage') ? 'storage_units' : 'trolleys';
      try {
          const res = await fetch(`/api/test_equipment/container/${containerType}/${encodeURIComponent(name)}`, {
              method: 'DELETE'
          });
          if (!res.ok) { const e = await res.json().catch(()=>({})); alert(e.error||'Löschen fehlgeschlagen'); return; }
          const result = await res.json();
          if (result.version_hash) dataVersionHash = result.version_hash;
      } catch(err) {
          alert('Netzwerkfehler beim Löschen.');
          return;
      }

      // Lokalen State bereinigen
      Object.values(hardwareInventory).forEach(part => {
          part.serials.forEach(s => { if (s.location === name) s.location = ''; });
      });
      if (currentEditType === 'storage') delete storageUnits[name];
      else delete trolleys[name];

      renderTrolleyTable();
      renderStorageTable();
      renderHardwareTable();
      closeTrolleyModal();
  }

document.getElementById('trolleyForm').addEventListener('submit', async e => {
      e.preventDefault();
      const oldName = document.getElementById('trolleyModalOriginalName').value;
      const newName = document.getElementById('trolleyModalName').value.trim();
      const engine = document.getElementById('trolleyModalEngine').value.trim();

      if(!newName) return;

      // --- UMBENENNUNG LOGIK (bereits granular via rename_storage) ---
      if (oldName && oldName !== newName) {
          const type = (currentEditType === 'storage') ? 'storage_units' : 'trolleys';
          try {
              const res = await fetch('/api/test_equipment/rename_storage', {
                  method: 'POST',
                  headers: {'Content-Type': 'application/json'},
                  body: JSON.stringify({ type, oldName, newName })
              });
              const result = await res.json();
              if(!res.ok) { alert(result.error); return; }
              if (result.version_hash) dataVersionHash = result.version_hash;

              if (currentEditType === 'storage') {
                  storageUnits[newName] = storageUnits[oldName];
                  delete storageUnits[oldName];
              } else {
                  trolleys[newName] = trolleys[oldName];
                  delete trolleys[oldName];
              }
              document.getElementById('trolleyModalOriginalName').value = newName;
          } catch(err) {
              alert("Fehler beim Umbenennen.");
              return;
          }
      }

      const targetDB = (currentEditType === 'storage') ? storageUnits : trolleys;
      const items = [];
      document.querySelectorAll('.trolley-item-row').forEach(row => {
          const id = row.dataset.itemId || null;
          const pn = row.querySelector('.trolley-pn-input').value.trim();
          const sn = row.querySelector('.trolley-sn-input').value.trim();
          const name = row.querySelector('.trolley-name-input').value.trim();
          const level = row.querySelector('.trolley-level-input').value.trim();
          const box = row.querySelector('.trolley-box-input').value.trim();
          const qty = parseInt(row.querySelector('.trolley-qty-input').value || 1);
          if(pn || name) items.push({ id, name, pn, sn, level, box, qty });
      });

      const responsible = document.getElementById('trolleyModalResponsible').value.trim();
      const images = [];
      document.querySelectorAll('#trolleyImagesList .trolley-img-item').forEach(div => {
          images.push({ path: div.dataset.path, name: div.dataset.name, stored_name: div.dataset.stored });
      });
      const containerData = { engineType: engine, responsible: responsible, images: images, level_images: currentLevelImages, items: items };

      try {
          await saveContainer(newName, containerData);
      } catch(err) {
          alert('Fehler beim Speichern: ' + err.message);
          return;
      }

      // Lokalen State aktualisieren (für konsistentes Rendering)
      targetDB[newName] = containerData;
      assignInventoryToTrolley(newName, items);

      renderTrolleyTable();
      renderStorageTable();
      renderHardwareTable();
      closeTrolleyModal();
  });

  function downloadTrolleyBOM(name) {
      // Prüfen, wo wir suchen müssen (Trolley oder Storage?)
      // Da der Name eindeutig sein sollte, suchen wir einfach in beiden oder nutzen currentEditType falls gesetzt
      let data = trolleys[name];
      let typeLabel = "RUESTWAGEN";
      
      if (!data && storageUnits[name]) {
          data = storageUnits[name];
          typeLabel = "LAGERORT";
      }
      
      if (!data) return;

      let content = `BOM - ${typeLabel}: ${name}\n`;
      content += `BEREICH / ENGINE: ${data.engineType}\n`;
      content += `DATUM: ${new Date().toLocaleDateString()}\n`;
      content += `----------------------------------------------------------------------------------------------------\n`;
      content += `LEVEL | BOX        | QTY  | PART NUMBER      | SERIAL NUMBER    | DESCRIPTION\n`;
      content += `----------------------------------------------------------------------------------------------------\n`;

      // Sortierung anwenden (selbe Logik wie im Modal)
      const items = [...(data.items || [])].sort((a, b) => {
          const levelA = String(a.level || '');
          const levelB = String(b.level || '');
          const levelCompare = levelA.localeCompare(levelB, undefined, { numeric: true, sensitivity: 'base' });
          if (levelCompare !== 0) return levelCompare;
          const boxA = String(a.box || '');
          const boxB = String(b.box || '');
          return boxA.localeCompare(boxB, undefined, { numeric: true, sensitivity: 'base' });
      });

      items.forEach(item => {
          const part = hardwareInventory[item.pn];
          const desc = (part && part.name) ? part.name : (item.name || "Unknown");
          
          // Formatierung mit padEnd für saubere Spalten
          const levelStr = String(item.level || '').padEnd(5, ' ');
          const boxStr = String(item.box || '').padEnd(10, ' '); // 10 Zeichen für Box
          const qtyStr = String(item.qty || 1).padEnd(4, ' ');   // 4 Zeichen für Menge
          const pnStr = String(item.pn).padEnd(16, ' ');
          const snStr = String(item.sn || "---").padEnd(16, ' ');
          // Beschreibung darf den Rest nehmen, aber wir kürzen sie bei Bedarf
          const descStr = desc.substring(0, 40); 

          content += `${levelStr} | ${boxStr} | ${qtyStr} | ${pnStr} | ${snStr} | ${descStr}\n`;
      });
      
      content += `----------------------------------------------------------------------------------------------------\n`;
      content += `GESAMT POSITIONEN: ${items.length}\n`;

      const blob = new Blob([content], { type: 'text/plain' });
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `BOM_${name}.txt`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);
  }

  function generateTrolleyPrintView() {
      const name = document.getElementById('trolleyModalOriginalName').value;
      if (!name) return;

      const typeLabel = currentEditType === 'storage' ? 'Lagerort' : 'Rüstwagen';
      const engineType = document.getElementById('trolleyModalEngine').value || '–';
      const responsible = document.getElementById('trolleyModalResponsible').value || '–';
      const dateStr = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });

      // Items aus dem DOM lesen (aktueller Zustand)
      const items = [];
      document.querySelectorAll('.trolley-item-row').forEach(row => {
          const pn  = row.querySelector('.trolley-pn-input').value.trim();
          const sn  = row.querySelector('.trolley-sn-input').value.trim();
          const nm  = row.querySelector('.trolley-name-input').value.trim();
          const lvl = row.querySelector('.trolley-level-input').value.trim();
          const box = row.querySelector('.trolley-box-input').value.trim();
          const qty = parseInt(row.querySelector('.trolley-qty-input').value || 1);
          if (pn || nm) items.push({ pn, sn, name: nm, level: lvl, box, qty });
      });

      // Gesamtbilder
      const overviewImages = [];
      document.querySelectorAll('#trolleyImagesList .trolley-img-item').forEach(div => {
          overviewImages.push({ path: div.dataset.path, altText: div.dataset.name || '' });
      });

      // Items nach Level gruppieren (sortiert)
      const levelMap = {};
      items.forEach(item => {
          const key = item.level || '';
          if (!levelMap[key]) levelMap[key] = [];
          levelMap[key].push(item);
      });
      const sortedLevels = Object.keys(levelMap).sort((a, b) =>
          a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
      );

      const esc = s => String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
      const origin = window.location.origin;

      // Gesamtbild-HTML
      let overviewHtml = '';
      if (overviewImages.length > 0) {
          overviewHtml = `<div class="section-title">Gesamtbild</div>
          <div class="overview-images">
              ${overviewImages.map(img => `<img src="${origin}${esc(img.path)}" alt="${esc(img.altText)}" class="overview-img">`).join('')}
          </div>`;
      }

      // Ebenen-HTML (Level-Bild + Items je Ebene)
      let levelsHtml = '';
      const positionedLevels = sortedLevels.filter(l => l !== '');
      if (positionedLevels.length > 0) {
          levelsHtml = '<div class="section-title">Ebenenübersicht</div>';
          positionedLevels.forEach(lvl => {
              const lvlImg = currentLevelImages[lvl];
              const imgHtml = (lvlImg && lvlImg.path)
                  ? `<img src="${origin}${esc(lvlImg.path)}" alt="Ebene ${esc(lvl)}" class="level-img">`
                  : `<div class="level-img-placeholder">Kein Foto</div>`;
              const rows = levelMap[lvl].map(it => `
                  <tr>
                      <td>${esc(it.pn)}</td>
                      <td>${esc(it.sn)}</td>
                      <td>${esc(it.name)}</td>
                      <td class="text-center">${esc(it.qty)}</td>
                      <td>${esc(it.box)}</td>
                  </tr>`).join('');
              levelsHtml += `
              <div class="level-section">
                  <div class="level-image-col">
                      <div class="level-label">Ebene ${esc(lvl)}</div>
                      ${imgHtml}
                  </div>
                  <div class="level-items-col">
                      <table class="level-table">
                          <thead><tr><th>P/N</th><th>S/N</th><th>Bezeichnung</th><th>Anz.</th><th>Box</th></tr></thead>
                          <tbody>${rows}</tbody>
                      </table>
                  </div>
              </div>`;
          });
      }

      // Vollständige BOM (alle Items, sortiert nach Level → Box)
      const allSorted = [...items].sort((a, b) => {
          const lc = String(a.level||'').localeCompare(String(b.level||''), undefined, { numeric: true });
          return lc !== 0 ? lc : String(a.box||'').localeCompare(String(b.box||''), undefined, { numeric: true });
      });
      const bomRows = allSorted.map((it, idx) => `
          <tr class="${idx % 2 === 0 ? 'even' : ''}">
              <td>${esc(it.level)}</td>
              <td>${esc(it.box)}</td>
              <td class="mono">${esc(it.pn)}</td>
              <td class="mono">${esc(it.sn)}</td>
              <td>${esc(it.name)}</td>
              <td class="text-center">${esc(it.qty)}</td>
          </tr>`).join('');

      const html = `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>${esc(typeLabel)} ${esc(name)} – BOM</title>
<style>
  @page { size: A4 portrait; margin: 1.2cm 1.4cm; }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: Arial, Helvetica, sans-serif; font-size: 9.5pt; color: #1e293b; background: white; }
  .header { display: flex; justify-content: space-between; align-items: flex-start; padding-bottom: 10px; margin-bottom: 12px; border-bottom: 2.5px solid #1e293b; }
  .header-left .title { font-size: 20pt; font-weight: 800; line-height: 1.1; }
  .header-left .subtitle { font-size: 10.5pt; color: #475569; margin-top: 3px; }
  .header-right { text-align: right; font-size: 8.5pt; color: #475569; line-height: 1.6; }
  .header-right strong { color: #1e293b; }
  .section-title { font-size: 7.5pt; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; margin: 12px 0 5px; padding-bottom: 3px; border-bottom: 1px solid #e2e8f0; }
  .overview-images { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; }
  .overview-img { max-height: 320px; max-width: 420px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 5px; }
  .level-section { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 7px; padding: 8px; border: 1px solid #e2e8f0; border-radius: 5px; page-break-inside: avoid; background: #fafafa; }
  .level-image-col { flex-shrink: 0; width: 190px; }
  .level-label { font-size: 9pt; font-weight: 700; color: #1e293b; margin-bottom: 5px; }
  .level-img { width: 186px; max-height: 160px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: white; }
  .level-img-placeholder { width: 186px; height: 100px; background: #f1f5f9; display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 7.5pt; border-radius: 4px; border: 1px dashed #cbd5e1; }
  .level-items-col { flex-grow: 1; }
  .level-table { width: 100%; border-collapse: collapse; font-size: 8pt; }
  .level-table th { background: #f1f5f9; color: #475569; font-weight: 600; padding: 3px 5px; text-align: left; border-bottom: 1px solid #cbd5e1; }
  .level-table td { padding: 2px 5px; border-bottom: 1px solid #f1f5f9; }
  .bom-table { width: 100%; border-collapse: collapse; font-size: 8pt; }
  .bom-table th { background: #1e293b; color: white; padding: 4px 7px; text-align: left; font-weight: 600; }
  .bom-table td { padding: 3px 7px; border-bottom: 1px solid #f1f5f9; }
  .bom-table tr.even td { background: #f8fafc; }
  .text-center { text-align: center; }
  .mono { font-family: monospace; font-size: 8pt; }
  .footer { margin-top: 14px; padding-top: 7px; border-top: 1px solid #e2e8f0; font-size: 7.5pt; color: #94a3b8; display: flex; justify-content: space-between; }
  @media print { body { print-color-adjust: exact; -webkit-print-color-adjust: exact; } }
</style>
</head>
<body>
  <div class="header">
    <div class="header-left">
      <div class="title">${esc(name)}</div>
      <div class="subtitle">${esc(typeLabel)} &nbsp;·&nbsp; ${esc(engineType)}</div>
    </div>
    <div class="header-right">
      <div><strong>Verantwortlich:</strong> ${esc(responsible)}</div>
      <div><strong>Positionen:</strong> ${items.length}</div>
      <div><strong>Erstellt:</strong> ${esc(dateStr)}</div>
    </div>
  </div>

  ${overviewHtml}
  ${levelsHtml}

  <div class="section-title">Stückliste (BOM) – alle Positionen</div>
  <table class="bom-table">
    <thead>
      <tr><th>Ebene</th><th>Box</th><th>P/N</th><th>S/N</th><th>Bezeichnung</th><th>Anz.</th></tr>
    </thead>
    <tbody>${bomRows}</tbody>
  </table>

  <div class="footer">
    <span>Operation Tool &nbsp;·&nbsp; ${esc(typeLabel)} <strong>${esc(name)}</strong></span>
    <span>Gedruckt: ${esc(dateStr)}</span>
  </div>
  <script>window.addEventListener('load', () => window.print());<\/script>
</body>
</html>`;

      const w = window.open('', '_blank');
      if (!w) { alert('Popup wurde blockiert. Bitte Popups für diese Seite erlauben.'); return; }
      w.document.write(html);
      w.document.close();
  }
    function downloadFilteredInventory() {
        let content = `HARDWARE INVENTORY EXPORT\n`;
        content += `DATUM: ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}\n`;
        
        const typeVal = document.getElementById('filterHwType').value;
        const prodVal = document.getElementById('filterHwProduct').value;
        const statusVal = document.getElementById('filterHwStatus').value;
        const locVal = document.getElementById('filterHwLocation').value;
        const term = document.getElementById('hardwareSearchInput').value;
    
        content += `FILTER: Type=${typeVal}, Prod=${prodVal}, Status=${statusVal}, Loc=${locVal}, Search="${term}"\n`;
        content += `----------------------------------------------------------------------------------------------------\n`;
        content += `P/N              | NAME                                     | QTY  | SERIAL NUMBERS [LOCATION]\n`;
        content += `----------------------------------------------------------------------------------------------------\n`;
    
        let pnCount = 0;
        let snCount = 0; // Neuer Zähler für S/N
    
        const rows = document.querySelectorAll('#hardwareInventoryTbody tr.pn-row');
        
        rows.forEach(row => {
            if (row.style.display === 'none') return;
    
            const pn = row.dataset.pn;
            const part = hardwareInventory[pn];
            if (!part) return;
    
            let relevantSerials = part.serials;
            if (statusVal !== 'all') relevantSerials = relevantSerials.filter(s => s.status === statusVal);
            if (locVal !== 'all') relevantSerials = relevantSerials.filter(s => s.location === locVal);
    
            if (relevantSerials.length === 0) return;
    
            pnCount++;
            snCount += relevantSerials.length; // Addiere S/N
    
            const pnStr = pn.padEnd(16, ' ');
            const nameStr = (part.name || 'Unbekannt').substring(0, 40).padEnd(40, ' ');
            const qtyStr = String(relevantSerials.length).padEnd(4, ' ');
            
            const details = relevantSerials.map(s => {
                if (s.location && s.location.trim()) {
                    return `${s.sn} [${s.location}]`;
                }
                return s.sn;
            }).join('   |   ');
            
            content += `${pnStr} | ${nameStr} | ${qtyStr} | ${details}\n`;
        });
    
        content += `----------------------------------------------------------------------------------------------------\n`;
        // Erweiterte Statistik
        content += `GESAMT: ${pnCount} P/N  (${snCount} S/N)\n`;
    
        const blob = new Blob([content], { type: 'text/plain' });
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a'); 
        a.href = url; 
        a.download = `Hardware_Export_${new Date().toISOString().slice(0,10)}.txt`; 
        document.body.appendChild(a); 
        a.click(); 
        document.body.removeChild(a); 
        window.URL.revokeObjectURL(url);
    }

    function downloadFilteredInventoryCSV() {
        const statusVal = document.getElementById('filterHwStatus').value;
        const locVal = document.getElementById('filterHwLocation').value;

        // Felder mit Kommas oder Anführungszeichen korrekt escapen
        const esc = v => {
            const s = String(v || '');
            return (s.includes(',') || s.includes('"')) ? `"${s.replace(/"/g, '""')}"` : s;
        };

        let csv = `part_number,name,type,product_type,description,sti_nr,serial_number,location,status,last_check\n`;

        const rows = document.querySelectorAll('#hardwareInventoryTbody tr.pn-row');
        rows.forEach(row => {
            if (row.style.display === 'none') return;
            const pn = row.dataset.pn;
            const part = hardwareInventory[pn];
            if (!part) return;

            let relevantSerials = part.serials;
            if (statusVal !== 'all') relevantSerials = relevantSerials.filter(s => s.status === statusVal);
            if (locVal !== 'all') relevantSerials = relevantSerials.filter(s => s.location === locVal);
            if (relevantSerials.length === 0) return;

            const prodType = Array.isArray(part.productType) ? part.productType.join(';') : (part.productType || '');
            relevantSerials.forEach(s => {
                csv += `${esc(pn)},${esc(part.name)},${esc(part.type)},${esc(prodType)},${esc(part.description)},${esc(part.stiNr||'')},${esc(s.sn)},${esc(s.location)},${esc(s.status)},${esc(s.lastCheck)}\n`;
            });
        });

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url; a.download = `Hardware_Export_${new Date().toISOString().slice(0,10)}.csv`;
        document.body.appendChild(a); a.click();
        document.body.removeChild(a); URL.revokeObjectURL(url);
    }

    function toggleDownloadDropdown() {
        const dropdown = document.getElementById('downloadDropdown');
        if (!dropdown.classList.contains('hidden')) {
            dropdown.classList.add('hidden');
            return;
        }
        const rect = document.getElementById('downloadDropdownWrapper').getBoundingClientRect();
        dropdown.style.top = (rect.bottom + 4) + 'px';
        dropdown.style.right = (window.innerWidth - rect.right) + 'px';
        dropdown.classList.remove('hidden');
    }
    function closeDownloadDropdown() {
        document.getElementById('downloadDropdown').classList.add('hidden');
    }
    function toggleAddDropdown() {
        const dropdown = document.getElementById('addDropdown');
        if (!dropdown.classList.contains('hidden')) {
            dropdown.classList.add('hidden');
            return;
        }
        const rect = document.getElementById('addDropdownWrapper').getBoundingClientRect();
        dropdown.style.top = (rect.bottom + 4) + 'px';
        dropdown.style.right = (window.innerWidth - rect.right) + 'px';
        dropdown.classList.remove('hidden');
    }
    function closeAddDropdown() {
        document.getElementById('addDropdown').classList.add('hidden');
    }
    document.addEventListener('click', e => {
        const dlWrapper = document.getElementById('downloadDropdownWrapper');
        if (dlWrapper && !dlWrapper.contains(e.target)) closeDownloadDropdown();
        const addWrapper = document.getElementById('addDropdownWrapper');
        if (addWrapper && !addWrapper.contains(e.target)) closeAddDropdown();
    });

// --- INVENTORY ---
  function renderHardwareTable() {
    const tbody = document.getElementById('hardwareInventoryTbody');
    tbody.innerHTML = '';

    // Checkout-Lookup aufbauen: key = item.id oder "pn|sn"
    const checkedOutMap = {};
    [...Object.entries(trolleys), ...Object.entries(storageUnits)].forEach(([cName, c]) => {
        (c.items || []).forEach(item => {
            if (!item.checkedOutBy) return;
            const key = item.id || (item.pn + '|' + item.sn);
            if (key) checkedOutMap[key] = { by: item.checkedOutBy, at: item.checkedOutAt, reason: item.checkedOutReason || '' };
        });
    });

    // "assigned" Zählvariable entfernt
    let totalSN = 0, maintenance = 0;

    // Sortieren der Keys für konsistente Anzeige
    const sortedPnKeys = Object.keys(hardwareInventory).sort();

    for (const pn of sortedPnKeys) {
        const part = hardwareInventory[pn];
        const qty = part.serials.length;
        totalSN += qty;

        // Status Zählung für die Bubbles
        const counts = part.serials.reduce((a, s) => { a[s.status] = (a[s.status] || 0) + 1; return a; }, {});

        // Zuweisung zu "assigned" entfernt
        maintenance += counts['Maintenance'] || 0;

        // 1. Status Overview (Bubbles) generieren
        let overview = '<div class="flex items-center gap-1">';
        if (Object.keys(counts).length > 0) {
            overview += Object.entries(counts).map(([status, count]) => {
                let bgClass = 'bg-red-500'; // Default (Decommissioned/Unknown)
                if (status === 'Available') bgClass = 'bg-green-500';
                    else if (status === 'Maintenance') bgClass = 'bg-yellow-500';
                    else if (status === 'In Order') bgClass = 'bg-slate-400'; // Grau für Bestellt
                return `<div title="${count} ${status}" class="status-indicator ${bgClass}">${count}</div>`;
            }).join('');
        } else {
            overview += '<span class="text-xs italic text-slate-400">No serials</span>';
        }
        overview += '</div>';

        // 2. Product Type Tags generieren
        let prodTypeHtml = '';
        const types = Array.isArray(part.productType) ? part.productType : [part.productType];
        types.forEach(t => { if(t) prodTypeHtml += `<span class="product-type-tag">${t}</span>`; });

        // --- HAUPTZEILE (PART NUMBER) ---
        const pnRow = document.createElement('tr');
        pnRow.className = 'pn-row border-b border-slate-200';
        pnRow.dataset.pn = pn;

        const isSelected = selectedPartNumbers.has(pn);

        pnRow.innerHTML = `
            <td class="px-4 py-4 text-center border-r border-slate-100">
                <input type="checkbox" class="pn-checkbox h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" 
                       value="${pn}" ${isSelected ? 'checked' : ''} onclick="event.stopPropagation(); togglePnSelection('${pn}')">
            </td>
            <td class="px-6 py-4">
                <button class="toggle-sn-rows flex items-center gap-2 text-slate-600 hover:text-blue-600 font-mono text-sm font-medium">
                    <span class="material-icons-outlined text-base">chevron_right</span>${pn}
                </button>
            </td>
            <td class="px-6 py-4 font-medium text-slate-800">${part.name}</td>
            <td class="px-6 py-4 text-slate-600">${part.type}</td>
            <td class="px-6 py-4 text-slate-600">${prodTypeHtml}</td>
            <td class="px-6 py-4 text-slate-600 font-mono">${qty}</td>
            <td class="px-6 py-4">${overview}</td>
            <td class="px-6 py-4 text-right">
                <button onclick="openPartNumberModal('${pn}')" class="text-slate-400 hover:text-blue-600 p-1 rounded-full hover:bg-slate-100 transition-colors">
                    <span class="material-icons-outlined text-lg">edit_note</span>
                </button>
            </td>`;
        tbody.appendChild(pnRow);

        // --- UNTERZEILEN (SERIAL NUMBERS) ---
        part.serials.forEach(s => {
            const snRow = document.createElement('tr');
            snRow.className = 'sn-row border-b border-slate-100';
            snRow.dataset.parentPn = pn;
            snRow.dataset.sn = s.sn;
            if (s.id) snRow.dataset.hwid = s.id;

            // Status Badge Farbe
            let badgeClass = 'bg-gray-100 text-gray-800';
            if (s.status === 'Available') badgeClass = 'badge-available';
            if (s.status === 'Maintenance') badgeClass = 'badge-maintenance';
            if (s.status === 'Decommissioned') badgeClass = 'badge-decommissioned';
            if (s.status === 'In Order') badgeClass = 'badge-in-order'; // NEU

            // --- NEUE LOGIK: MAINTENANCE LINKS & ISSUES ---
            let maintHtml = '<span class="text-xs text-slate-400 italic">- Keine -</span>';
            const links = s.maintenanceLinks || []; 
            const statusInfo = s.statusSummary || { light: 'GREEN', openIssues: 0, issueList: [] };

            // Migration / Fallback: Falls noch keine Liste, aber alte ID da ist
            if (links.length === 0 && s.maintenanceId && s.maintenanceId !== 'N/A') {
                links.push({ id: s.maintenanceId, lastCheck: s.lastCheck || 'N/A' });
            }

            // ANZEIGEN, WENN: Es Wartungspläne gibt ODER offene Issues
            if (links.length > 0 || statusInfo.openIssues > 0) {
                
                // --- AMPEL LOGIK ---
                let badgeColor = 'bg-green-100 text-green-700 border-green-200'; // Default Grün
                let icon = 'check_circle';

                if (statusInfo.light === 'RED') {
                    badgeColor = 'bg-red-100 text-red-700 border-red-200';
                    icon = 'error';
                } else if (statusInfo.light === 'AMBER') {
                    badgeColor = 'bg-amber-100 text-amber-800 border-amber-200';
                    icon = 'schedule';
                } else if (statusInfo.light === 'YELLOW') {
                    badgeColor = 'bg-yellow-100 text-yellow-800 border-yellow-200';
                    icon = 'warning';
                } else if (statusInfo.light === 'BLUE') {
                    badgeColor = 'bg-blue-100 text-blue-700 border-blue-200';
                    icon = 'event_upcoming';
                }

                // Info-Text bauen
                let countLabel = '';
                
                if (links.length > 0) {
                    countLabel = links.length === 1 ? '1 Plan' : `${links.length} Pläne`;
                } else {
                    // Fall: Keine Pläne, aber Issues (z.B. Ad-Hoc)
                    countLabel = 'Ad-Hoc';
                }
                
                if (statusInfo.openIssues > 0) {
                    // Wenn wir Pläne haben, hängen wir die Issues an ("1 Plan (1 Issue)")
                    // Wenn nicht, zeigen wir nur die Issues ("Ad-Hoc (1 Issue)") oder direkt "1 Issue"
                    if (links.length > 0) {
                         countLabel += ` (${statusInfo.openIssues} Issue${statusInfo.openIssues > 1 ? 's' : ''})`;
                    } else {
                         countLabel = `${statusInfo.openIssues} Issue${statusInfo.openIssues > 1 ? 's' : ''}`;
                    }
                }
                // -------------------

                let popoverRows = '';
                if (links.length > 0) {
                    links.forEach(l => {
                        const targetUrl = `/maintenance?openId=${l.id}`;
                        const displayTitle = l.title || l.id;
                        
                        popoverRows += `
                            <div class="flex justify-between items-center p-2 border-b border-slate-100 last:border-0 hover:bg-slate-50">
                                <div class="overflow-hidden mr-2">
                                    <div class="font-bold text-xs text-slate-700 truncate" title="${displayTitle}">${displayTitle}</div>
                                    <div class="text-[10px] text-slate-500 font-mono">${l.id} | Last: ${l.lastCheck}</div>
                                </div>
                                <a href="${targetUrl}" target="_blank" class="text-blue-600 hover:text-blue-800 p-1 bg-white border border-slate-200 rounded hover:bg-blue-50 transition-colors flex-shrink-0" title="Zum Wartungsplan springen">
                                    <span class="material-icons-outlined text-sm align-middle">open_in_new</span>
                                </a>
                            </div>
                        `;
                    });
                } else {
                    popoverRows = '<div class="p-2 text-[10px] text-slate-400 italic">Keine verknüpften Wartungspläne.</div>';
                }

                // --- ISSUE LISTE BAUEN ---
                let issueRows = '';
                if (statusInfo.issueList && statusInfo.issueList.length > 0) {
                    // Header für Issues
                    issueRows += `<div class="text-[10px] font-bold text-red-500 uppercase mb-1 px-2 pt-2 border-t border-slate-100 bg-red-50">Offene Probleme</div>`;

                    statusInfo.issueList.forEach(iss => {
                        const targetUrl = `/maintenance?openId=${iss.id}`;
                        const prioClass = iss.priority === 'Critical' ? 'text-red-700 font-bold' : 'text-slate-700';

                        issueRows += `
                            <div class="flex justify-between items-start p-2 border-b border-red-100 last:border-0 hover:bg-red-50 bg-red-50/30">
                                <div class="overflow-hidden mr-2">
                                    <div class="font-bold text-xs ${prioClass} truncate" title="${iss.title}">${iss.title}</div>
                                    <div class="text-[10px] text-slate-500 font-mono">${iss.id}</div>
                                </div>
                                <a href="${targetUrl}" target="_blank" class="text-red-600 hover:text-red-800 p-1 bg-white border border-red-200 rounded hover:bg-red-100 transition-colors flex-shrink-0" title="Issue öffnen">
                                    <span class="material-icons-outlined text-sm align-middle">report_problem</span>
                                </a>
                            </div>
                        `;
                    });
                }

                // --- HTML ZUSAMMENBAUEN ---
                maintHtml = `
                    <div class="maint-popover-wrapper group">
                        <span class="cursor-help inline-flex items-center px-2 py-0.5 rounded text-xs font-bold border ${badgeColor}">
                            <span class="material-icons-outlined text-[10px] mr-1">${icon}</span>
                            ${countLabel}
                        </span>
                        <div class="maint-popover-content">
                            <!-- Maintenance Plans -->
                            <div class="text-[10px] font-bold text-slate-400 uppercase mb-1 px-2 pt-1 border-b border-slate-100 bg-slate-50 rounded-t">Verknüpfte Wartung</div>
                            <div class="max-h-32 overflow-y-auto custom-scrollbar">
                                ${popoverRows}
                            </div>

                            <!-- Issues (Neu) -->
                            <div class="max-h-32 overflow-y-auto custom-scrollbar">
                                ${issueRows}
                            </div>

                            ${statusInfo.nextDue && statusInfo.nextDue !== 'N/A' ? `<div class="text-[10px] text-slate-500 px-2 py-1 border-t border-slate-100 bg-slate-50 rounded-b">Nächste Fälligkeit: <strong>${new Date(statusInfo.nextDue).toLocaleDateString('de-DE')}</strong></div>` : ''}
                        </div>
                    </div>
                `;
            }

            // Checkout-Status prüfen
            const checkoutInfo = checkedOutMap[s.id] || checkedOutMap[pn + '|' + s.sn];
            let checkoutBadgeHtml = '';
            if (checkoutInfo) {
                const elapsed = timeAgo(checkoutInfo.at);
                const tipRaw = checkoutInfo.reason
                    ? `${checkoutInfo.by} · ${elapsed} · ${checkoutInfo.reason}`
                    : `${checkoutInfo.by} · ${elapsed}`;
                const tip = tipRaw.replace(/"/g, '&quot;');
                checkoutBadgeHtml = `<div class="mt-1"><span class="cursor-help inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-800 border border-orange-200" title="${tip}"><span class="material-icons-outlined text-[10px]">logout</span>Ausgebucht</span></div>`;
            }

            // Location Anzeige mit Tooltip (Level/Box)
            let locationHtml = `<span class="text-slate-500 ">${s.location || '-'}</span>`;
            
            const containerData = trolleys[s.location] || storageUnits[s.location];
            
            if (containerData) {
                const isTrolley = !!trolleys[s.location];
                const badgeClass = isTrolley 
                    ? 'bg-orange-100 text-orange-800 border-orange-200  ' 
                    : 'bg-indigo-100 text-indigo-800 border-indigo-200';
                const icon = isTrolley ? 'trolley' : 'shelves';
                const itemDetail = (containerData.items || []).find(i => i.pn === pn && i.sn === s.sn);
                
                let popoverContent = '';
                
                if (itemDetail && (itemDetail.level || itemDetail.box)) {
                    popoverContent = `
                        <div class="maint-popover-content" style="width: auto; min-width: 120px;">
                            <div class="text-[10px] font-bold text-slate-400 uppercase mb-1 px-2 pt-1 border-b border-slate-100 bg-slate-50 rounded-t">Detailposition</div>
                            <div class="p-2 text-xs text-slate-700">
                                ${itemDetail.level ? `<div><strong>Ebene:</strong> ${itemDetail.level}</div>` : ''}
                                ${itemDetail.box ? `<div><strong>Box:</strong> ${itemDetail.box}</div>` : ''}
                            </div>
                        </div>
                    `;
                }

                // Wrapper für Hover-Effekt
                locationHtml = `
                    <div class="maint-popover-wrapper relative inline-block">
                        <span class="cursor-help inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border ${badgeClass}">
                            ${s.location}
                        </span>
                        ${popoverContent}
                    </div>
                `;
            }
            // --- NEU: DOKUMENTE AUFBEREITEN ---
            let docsHtml = '';
            if (s.documents && s.documents.length > 0) {
                docsHtml = '<div class="flex flex-wrap gap-2 mt-1">';
                s.documents.forEach(doc => {
                    const isImg = doc.name.match(/\.(jpg|jpeg|png|gif|webp)$/i);
                    if (isImg) {
                        // BILD: Thumbnail + Lightbox
                        docsHtml += `
                            <div class="relative group cursor-zoom-in" onclick="event.stopPropagation(); openLightbox('${doc.path}', '${doc.name}')">
                                <img src="${doc.path}" class="w-8 h-8 object-cover rounded border border-slate-200 shadow-sm hover:border-blue-400 transition-colors">
                            </div>`;
                    } else {
                        // DATEI: Icon + Link
                        docsHtml += `
                            <a href="${doc.path}" target="_blank" class="flex items-center justify-center w-8 h-8 bg-slate-50 border border-slate-200 rounded text-slate-500 hover:text-blue-600 hover:border-blue-300 transition-colors" title="${doc.name}" onclick="event.stopPropagation()">
                                <span class="material-icons-outlined text-base">description</span>
                            </a>`;
                    }
                });
                docsHtml += '</div>';
            } else {
                docsHtml = '<span class="text-xs text-slate-300 italic">- Keine -</span>';
            }
            // ----------------------------------

            // --- 4. GRID LAYOUT ZUSAMMENBAUEN ---
            snRow.innerHTML = `
                <td class="px-6 py-3 sn-details-cell text-sm text-slate-700" colspan="8">
                    <div class="grid grid-cols-1 md:grid-cols-8 gap-4 items-start">

                        <!-- S/N -->
                        <div>
                            <div class="text-xs text-slate-400 uppercase font-semibold mb-1">Serial No.</div>
                            <div class="font-mono font-medium text-slate-800">${s.sn}</div>
                        </div>

                        <!-- Location -->
                        <div>
                            <div class="text-xs text-slate-400 uppercase font-semibold mb-1">Location</div>
                            <div class="inline-block">${locationHtml}</div>
                            ${checkoutBadgeHtml}
                        </div>

                        <!-- STI-Nr. -->
                        <div>
                            <div class="text-xs text-slate-400 uppercase font-semibold mb-1">STI-Nr.</div>
                            <div class="font-mono text-slate-700">${part.stiNr || '-'}</div>
                        </div>

                        <!-- Wartung (Breiter) -->
                        <div class="col-span-2">
                            <div class="text-xs text-slate-400 uppercase font-semibold mb-1">Wartungs-Status</div>
                            <div class="inline-block">${maintHtml}</div>
                        </div>
                        
                        <!-- VERSCHOBEN: DOKUMENTE -->
                        <div>
                            <div class="text-xs text-slate-400 uppercase font-semibold mb-1">Dokumente</div>
                            ${docsHtml}
                        </div>

                        <!-- Kit -->
                        <div>
                            <div class="text-xs text-slate-400 uppercase font-semibold mb-1">Assigned Kit</div>
                            <div class="font-medium ${s.kitId !== 'N/A' ? 'text-blue-600' : 'text-slate-400'}">
                                ${getKitName(s.kitId)}
                            </div>
                        </div>

                        <!-- Status -->
                        <div class="flex items-center justify-end h-full">
                            <span class="status-badge ${badgeClass}">${s.status}</span>
                        </div>
                    </div>
                </td>`;

            tbody.appendChild(snRow);
        });
    }

    // KPIs aktualisieren
    document.getElementById('totalPnCount').textContent = Object.keys(hardwareInventory).length;
    document.getElementById('totalSnCount').textContent = totalSN;
    // Aktualisierung für "assignedCount" entfernt
    document.getElementById('maintenanceCount').textContent = maintenance;
  }
  document.getElementById('hardwareInventoryTbody').addEventListener('click', e => { const btn = e.target.closest('.toggle-sn-rows'); if (btn) { const pn = btn.closest('tr').dataset.pn; btn.classList.toggle('is-open'); document.querySelectorAll(`.sn-row[data-parent-pn="${pn}"]`).forEach(r => r.classList.toggle('is-visible')); } });
let isPartEditMode = false;
    function togglePartNumberEditMode(enable) {
        isPartEditMode = enable;
    
        // 1. Header & Footer
        document.getElementById('btnEditPart').style.display = enable ? 'none' : 'block';
        document.getElementById('partEditButtons').classList.toggle('hidden', !enable);
        document.getElementById('partViewButtons').classList.toggle('hidden', enable);
        document.getElementById('partNumberModalSubtitle').textContent = enable ? 'Bearbeiten' : 'Details anzeigen';
        
        // Delete Button nur wenn wir ein bestehendes Item bearbeiten (nicht bei Neu)
        const isNew = !document.getElementById('pnModal_originalPartNumber').value;
        document.getElementById('deletePartBtn').classList.toggle('hidden', !enable || isNew);
    
        // 2. Add Button
        document.getElementById('btnAddSerial').classList.toggle('hidden', !enable);
    
        // 3. Haupt-Formular Inputs (P/N, Name, Desc, Type)
        const mainInputs = document.querySelectorAll('#partNumberForm > div input, #partNumberForm > div textarea');
        mainInputs.forEach(inp => {
            inp.readOnly = !enable;
            if (enable) {
                inp.classList.remove('bg-slate-50', 'border-transparent', 'text-slate-600');
                inp.classList.add('bg-white', 'border-slate-300');
            } else {
                        inp.classList.add('bg-slate-50', 'border-slate-200', 'text-slate-700');
                        inp.classList.remove('bg-white', 'border-slate-300', 'border-transparent', 'text-slate-600');
                    }
        });
        
        // Tag Input (Product Type)
        const tagInput = document.getElementById('pnModal_productInput');
        if(tagInput) {
            tagInput.style.display = enable ? 'block' : 'none';
            // Remove-Buttons an den Tags
            document.querySelectorAll('#pnModal_productTags .tag-chip-remove').forEach(btn => {
                btn.style.display = enable ? 'inline' : 'none';
            });
        }
    
        // 4. Serial Number Blöcke
        document.querySelectorAll('.sn-block').forEach(block => {
            // Inputs im Block
            block.querySelectorAll('input, select').forEach(inp => {
                inp.disabled = !enable; // Selects müssen disabled sein
                inp.readOnly = !enable; // Inputs readonly
                
                if (enable) {
                    inp.classList.remove('bg-transparent', 'border-transparent', 'font-bold', 'text-slate-800');
                    inp.classList.add('bg-white', 'border-slate-300');
                    if(inp.tagName === 'SELECT') inp.classList.remove('appearance-none');
                } else {
                    // View Mode: Subtiler Rahmen und Hintergrund
                    inp.classList.add('bg-slate-50', 'border-slate-200', 'text-slate-700');
                    inp.classList.remove('bg-white', 'border-slate-300', 'bg-transparent', 'border-transparent');
                    
                    // Spezielles Styling für View Mode
                    if (inp.classList.contains('sn-input')) {
                        inp.classList.add('font-bold', 'text-slate-900'); // Etwas dunkler für S/N
                    }
                    if(inp.tagName === 'SELECT') inp.classList.add('appearance-none');
                }
            });
    
            // Delete Button im Block
            const delBtn = block.querySelector('.sn-delete-btn'); // Statt onclick*="remove"
            if(delBtn) delBtn.style.display = enable ? 'block' : 'none';
            
            // Upload Button
            const uploadLabel = block.querySelector('.docs-section label.cursor-pointer');
            if(uploadLabel) uploadLabel.style.display = enable ? 'inline-flex' : 'none';
            
            // Delete Buttons bei Dokumenten
            block.querySelectorAll('.doc-item button').forEach(btn => {
                btn.style.display = enable ? 'block' : 'none';
            });
        });
    }
  // --- PART NUMBER MODAL ---
  function openPartNumberModal(pn) { 
    const f = document.getElementById('partNumberForm'); 
    f.reset(); 
    document.getElementById('serialNumbersContainer').innerHTML=''; 
    
    // Tag Input Reset
    currentProductTags = [];
    renderProductTags();
    setupProductTagInput();

    if(pn){ 
        // EDIT (Startet im View Mode)
        document.getElementById('partNumberModalTitle').textContent = `${pn}`; 
        const p = hardwareInventory[pn]; 
        
        document.getElementById('pnModal_partNumber').value = pn; 
        document.getElementById('pnModal_originalPartNumber').value = pn; 
        document.getElementById('pnModal_hardwareType').value = p.type;
        document.getElementById('pnModal_name').value = p.name || '';
        document.getElementById('pnModal_description').value = p.description;
        document.getElementById('pnModal_stiNr').value = p.stiNr || '';
        
        currentProductTags = Array.isArray(p.productType) ? [...p.productType] : [p.productType];
        renderProductTags();
        
        p.serials.forEach(s => addSerialNumberBlock(s)); 
        
        deletePartBtn.onclick = () => deletePart(pn); 
        
        // START VIEW MODE
        togglePartNumberEditMode(false);
    }
    else{ 
        // NEW (Startet im Edit Mode)
        document.getElementById('partNumberModalTitle').textContent = 'New Part Number'; 
        document.getElementById('pnModal_originalPartNumber').value = ''; 
        
        currentProductTags = []; 
        renderProductTags();
        
        addSerialNumberBlock(); 
        
        // START EDIT MODE
        togglePartNumberEditMode(true);
        // Edit Button verstecken bei Neu
        document.getElementById('btnEditPart').style.display = 'none';
    } 
    
    openModal(partNumberModal); 
  }

  function closePartNumberModal(){closeModal(partNumberModal);}
  function deletePart(pn){ if(confirm(`Delete part "${pn}"?`)){ delete hardwareInventory[pn]; updateAndReassignKits(); saveDataToServer(); closePartNumberModal(); } }
    function addSerialNumberBlock(d=null){ 
          const c=document.getElementById('serialNumbersContainer'); 
          const b=document.createElement('div'); 
          b.className='p-4 border border-slate-200 rounded-lg bg-slate-50 space-y-3 relative sn-block'; 
          
          const s = {
              id: d?.id || '',
              sn: d?.sn || '',
              loc: d?.location || '',
              chk: d?.lastCheck || '',
              st: d?.status || 'Available',
              mid: d?.maintenanceId || 'N/A',
              // NEU: Dokumente laden
              docs: d?.documents || [] 
          };
    
          // HTML Aufbau
          b.innerHTML=`
            <input type="hidden" class="id-input" value="${s.id}">
            
            <!-- Header Buttons (Delete & Docs) -->
            <div class="absolute top-2 right-2 flex gap-1">
                <button type="button" class="text-blue-400 hover:text-blue-600 transition-colors p-1" title="Dokumente/Bilder" onclick="this.closest('.sn-block').querySelector('.docs-section').classList.toggle('hidden')">
                    <span class="material-icons-outlined text-lg">attachment</span>
                </button>
                <button type="button" class="sn-delete-btn text-slate-400 hover:text-red-500 transition-colors p-1" onclick="this.closest('.sn-block').remove()">
                    <span class="material-icons-outlined text-lg">delete</span>
                </button>
            </div>
    
            <div class="grid md:grid-cols-3 gap-4">
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">S/N</label>
                    <input type="text" class="sn-input w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500" value="${s.sn}" required>
                </div>
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Location</label>
                    <input type="text" class="location-input w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500" value="${s.loc}">
                </div>
                <div class="hidden"> 
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Wartung ID</label>
                    <input type="text" class="maintenanceid-input w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500" value="${s.mid}">
                </div>
            </div>
            <div class="grid md:grid-cols-2 gap-4">
                <div class="hidden">
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Last Check</label>
                    <input type="date" class="lastcheck-input w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500" value="${s.chk}">
                </div>
                <div>
                    <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Status</label>
                    <select class="status-select w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500 bg-white">
                        ${['Available','Maintenance','In Order','Decommissioned'].map(o=>`<option ${s.st===o?'selected':''}>${o}</option>`).join('')}     
                    </select>
                </div>
            </div>
    
            <!-- DOKUMENTEN SEKTION (Standardmäßig versteckt) -->
            <div class="docs-section hidden mt-4 pt-3 border-t border-slate-200 bg-slate-100/50 -mx-4 px-4 pb-2 docs-container">
                <label class="block text-xs font-bold text-slate-500 uppercase mb-2">Dokumente & Bilder</label>
                <div class="docs-list mb-2"></div>
                <label class="cursor-pointer inline-flex items-center gap-2 text-xs font-medium text-blue-600 hover:text-blue-800 bg-white border border-blue-200 px-3 py-1.5 rounded transition-colors">
                    <span class="material-icons-outlined text-sm">upload</span> Datei hochladen
                    <input type="file" class="hidden" onchange="uploadHardwareFile(this)">
                </label>
            </div>
          `; 
          
          c.appendChild(b); 
          
          // Existierende Docs rendern
          const list = b.querySelector('.docs-list');
          s.docs.forEach(doc => addDocItem(list, doc.path, doc.name, doc.stored_name));
    }
document.getElementById('partNumberForm').addEventListener('submit', async e => { 
    e.preventDefault();

    console.log("DEBUG: Formular partNumberForm wird verarbeitet...");

    const opn = document.getElementById('pnModal_originalPartNumber').value; 
    const npn = document.getElementById('pnModal_partNumber').value.trim(); 

    if(!npn) {
        alert("Part Number darf nicht leer sein.");
        return;
    }

    // Prüfen auf Duplikate
    if((!opn || opn !== npn) && hardwareInventory[npn]){ 
        alert(`ACHTUNG: Die Part Number "${npn}" existiert bereits!`); 
        return; 
    }

    // Seriennummern sammeln
    const sns=[]; 
    document.querySelectorAll('.sn-block').forEach(b=>{ 
        const snInput = b.querySelector('.sn-input');
        if(snInput) {
            
            // NEU: Dokumente einsammeln
            const docs = [];
            b.querySelectorAll('.doc-item').forEach(d => {
                docs.push({
                    path: d.dataset.path,
                    name: d.dataset.name,
                    stored_name: d.dataset.stored
                });
            });

            const sn = snInput.value.trim(); 
            if(sn) sns.push({
                id: b.querySelector('.id-input').value,
                sn, 
                location: b.querySelector('.location-input').value.trim(), 
                lastCheck: b.querySelector('.lastcheck-input').value, 
                status: b.querySelector('.status-select').value, 
                maintenanceId: b.querySelector('.maintenanceid-input').value.trim()||'N/A',
                documents: docs // <--- HIER SPEICHERN
            }); 
        }
    });

    const pd = {
        name: document.getElementById('pnModal_name').value.trim(),
        type: document.getElementById('pnModal_hardwareType').value.trim(),
        productType: currentProductTags,
        description: document.getElementById('pnModal_description').value.trim(),
        stiNr: document.getElementById('pnModal_stiNr').value.trim(),
        serials: sns
    };

    // Wenn umbenannt wurde, alten Eintrag löschen
    if(opn && opn !== npn){ 
        delete hardwareInventory[opn]; 
        // Optional: Hier müsste man auch BOMs aktualisieren, die auf die alte PN verweisen
    }

    // Speichern im lokalen Objekt
    hardwareInventory[npn] = pd; 

    // UI Updates
    updateAndReassignKits(); // Rendert die Tabelle neu

    // Speichern am Server
    await saveDataToServer(); 

    // Formular zurücksetzen, damit Safari nicht meckert
    e.target.reset(); 

    closePartNumberModal(); 
  });

    // --- BOM MODAL ---
    let isBomEditMode = false;
// --- NEUE FUNKTION: Toggle View/Edit Mode ---
function toggleBomEditMode(enable) {
    isBomEditMode = enable;

    // 1. Header Input Styling
    const nameInput = document.getElementById('modalKitNameInput');
    const codeInput = document.getElementById('modalKitCodeInput');
    nameInput.readOnly = !enable;
    codeInput.readOnly = !enable;
    if (enable) {
        // Edit Style
        nameInput.classList.remove('bg-transparent', 'border-transparent');
        nameInput.classList.add('bg-white', 'border-slate-300', 'focus:ring-2', 'focus:ring-blue-500', 'focus:border-transparent', 'px-2', 'py-1');
        codeInput.classList.remove('bg-transparent', 'border-transparent');
        codeInput.classList.add('bg-white', 'border-slate-300', 'focus:ring-2', 'focus:ring-blue-500', 'focus:border-transparent', 'px-2', 'py-1');
    } else {
        nameInput.classList.add('bg-transparent', 'border-transparent');
        nameInput.classList.remove('bg-white', 'border-slate-300', 'focus:ring-2', 'focus:ring-blue-500', 'focus:border-transparent', 'px-2', 'py-1');
        codeInput.classList.add('bg-transparent', 'border-transparent');
        codeInput.classList.remove('bg-white', 'border-slate-300', 'focus:ring-2', 'focus:ring-blue-500', 'focus:border-transparent', 'px-2', 'py-1');

        // Reset auf Originalwerte bei Abbrechen
        const id = document.getElementById('modalKitId').dataset.fullId;
        if(kitBoms[id]) {
            nameInput.value = kitBoms[id].name;
            codeInput.value = (kitBoms[id].code || '').toUpperCase();
        }
    }

    // 2. Buttons umschalten
    document.getElementById('btnEditBom').style.display = enable ? 'none' : 'block';
    document.getElementById('bomAddContainer').classList.toggle('hidden', !enable);
    
    // 3. Footer umschalten
    document.getElementById('bomViewFooter').classList.toggle('hidden', enable);
    document.getElementById('bomEditFooter').classList.toggle('hidden', !enable);

    // 4. Liste bearbeitbar machen (Inputs, Selects, Delete Buttons)
    const container = document.getElementById('bomItemsContainer');
    
    // Selects & Inputs
    container.querySelectorAll('select, input').forEach(el => {
        el.disabled = !enable;
        if (enable) {
            el.classList.remove('bg-slate-100', 'text-slate-500');
            el.classList.add('bg-white');
        } else {
            el.classList.add('bg-slate-100', 'text-slate-500');
            el.classList.remove('bg-white');
        }
    });

    // Delete Buttons
    container.querySelectorAll('.bom-delete-btn').forEach(btn => {
        btn.style.display = enable ? 'block' : 'none';
    });
}

function openBomModal(id) { 
    // ID speichern
    const idSpan = document.getElementById('modalKitId');
    idSpan.dataset.fullId = id; 
    
    // Daten holen
    const kitData = kitBoms[id];
    const displayName = kitData ? kitData.name : id;
    
    // Input Felder befüllen
    const nameInput = document.getElementById('modalKitNameInput');
    nameInput.value = displayName;
    document.getElementById('modalKitCodeInput').value = (kitData ? kitData.code || '' : '').toUpperCase();

    document.getElementById('bomItemsContainer').innerHTML = '';

    // Fallback für neues Kit
    if(!kitBoms[id]) {
        kitBoms[id] = { id: id, name: displayName, code: '', items: [], status: 'missing' };
    }
    
    const items = kitBoms[id].items || []; 
    if(items.length === 0) addBomItemRow('', 1); 
    else items.forEach(i => addBomItemRow(i.partNumber, i.quantity)); 
    
    // START IM VIEW MODE
    toggleBomEditMode(false);
    
    openModal(bomModal); 
}

function addBomItemRow(partNumber, quantity) {
    const div = document.createElement('div');
    div.className = 'bom-item-row bg-slate-50 p-4 rounded-lg border border-slate-200 relative group transition-all';

    let opts = '<option value="">-- Select P/N --</option>'; 
    Object.keys(hardwareInventory).sort().forEach(p => { 
        // Product Name anzeigen
        const pName = hardwareInventory[p].name || '';
        opts += `<option value="${p}" ${p === partNumber ? 'selected' : ''}>${p} - ${pName}</option>`; 
    });

    // STATUS LOGIK (für die Anzeige auch im Edit Mode nützlich)
    let statusIcon = '';
    let statusText = '';
    let assignedSerialsList = '<span class="text-slate-400 italic text-xs">None assigned</span>';
    const kitId = document.getElementById('modalKitId').dataset.fullId; // ID nutzen!

    if (partNumber) {
        const assignedSerials = hardwareInventory[partNumber]?.serials.filter(s => s.kitId === kitId) || [];
        const assignedCount = assignedSerials.length;

        if (assignedCount >= quantity) {
            statusIcon = '<span class="material-icons-outlined text-green-500 text-2xl">check_circle</span>';
            statusText = `<span class="text-xs text-green-600 font-bold">${assignedCount}/${quantity} Ready</span>`;
        } else if (assignedCount > 0) {
            statusIcon = '<span class="material-icons-outlined text-yellow-500 text-2xl">warning</span>';
            statusText = `<span class="text-xs text-yellow-600 font-bold">${assignedCount}/${quantity} Partial</span>`;
        } else {
            statusIcon = '<span class="material-icons-outlined text-red-500 text-2xl">error</span>';
            statusText = `<span class="text-xs text-red-600 font-bold">Missing All (${quantity})</span>`;
        }
        
        if (assignedCount > 0) {
            assignedSerialsList = assignedSerials.map(s => `<span class="inline-block bg-white border border-slate-200 text-slate-700 text-xs font-mono py-0.5 px-1.5 rounded">${s.sn}</span>`).join(' ');
        }
    }

    // Styling abhängig vom Modus (wird beim Initialisieren gesetzt)
    const disabledAttr = !isBomEditMode ? 'disabled' : '';
    const inputClass = !isBomEditMode ? 'bg-slate-100 text-slate-500' : 'bg-white';
    const deleteDisplay = !isBomEditMode ? 'none' : 'block';

    div.innerHTML = `
        <button type="button" onclick="removeBomItem(this)" class="bom-delete-btn absolute top-2 right-2 text-slate-400 hover:text-red-500 transition-colors p-1 rounded-full hover:bg-slate-100" style="display: ${deleteDisplay}" title="Remove item">
            <span class="material-icons-outlined text-lg">delete</span>
        </button>
        
        <div class="grid grid-cols-12 gap-4 items-start pr-6">
            <div class="col-span-1 flex justify-center pt-7">${statusIcon}</div>
            <div class="col-span-11">
                <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                    <div class="md:col-span-2">
                        <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Component</label>
                        <select class="w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500 ${inputClass}" ${disabledAttr}>${opts}</select>
                    </div>
                    <div>
                        <label class="block text-xs font-semibold text-slate-500 uppercase mb-1">Required</label>
                        <input type="number" value="${quantity}" min="1" class="w-full px-3 py-2 border border-slate-300 rounded-md text-sm focus:ring-blue-500 ${inputClass}" ${disabledAttr}>
                    </div>
                </div>
                <div class="mt-3 flex items-start gap-4 bg-white p-2 rounded border border-slate-100">
                    <div class="flex-1">
                        <span class="text-xs font-semibold text-slate-500 uppercase">Assigned S/N</span>
                        <div class="mt-1 flex flex-wrap gap-1">${assignedSerialsList}</div>
                    </div>
                    <div class="text-right flex-shrink-0">
                        <span class="text-xs font-semibold text-slate-500 uppercase">Status</span>
                        <div class="mt-1">${statusText}</div>
                    </div>
                </div>
            </div>
        </div>`;
        
    document.getElementById('bomItemsContainer').appendChild(div);
}
  function closeBomModal(){ closeModal(bomModal); }
  function addBomItem(){ addBomItemRow('',1); }
  function removeBomItem(b){ b.closest('.bom-item-row').remove(); }
  
    document.getElementById('bomForm').addEventListener('submit', e => { 
        e.preventDefault(); 
        
        // ID holen
        const id = document.getElementById('modalKitId').dataset.fullId; 
        
        // Namen holen
        const newName = document.getElementById('modalKitNameInput').value.trim();
        if (!newName) { alert("Kit Name darf nicht leer sein."); return; }
    
        const items = []; 
        document.querySelectorAll('#bomItemsContainer .bom-item-row').forEach(r => { 
            const p = r.querySelector('select').value; 
            const q = r.querySelector('input[type=number]').value; 
            if(p && q) items.push({ partNumber: p, quantity: parseInt(q) }); 
        }); 
        
        // Code holen und validieren
        const newCode = document.getElementById('modalKitCodeInput').value.trim().toUpperCase();
        const isDuplicateCode = newCode && Object.entries(kitBoms).some(([k, v]) => k !== id && (v.code || '').toUpperCase() === newCode);
        if (isDuplicateCode) { alert(`Code "${newCode}" wird bereits von einem anderen Kit verwendet.`); return; }

        if (kitBoms[id]) {
            kitBoms[id].items = items;
            kitBoms[id].name = newName;
            kitBoms[id].code = newCode;
        }
        
        closeBomModal(); 
        
        saveDataToServer().then(() => {
            // UI aktualisieren (besonders wichtig wegen Namensänderung)
            renderMappingTable(); 
            applyHardwareFilters(); // Falls in der Hardware-Liste Kit-Namen stehen
        }); 
    });

  // --- MAPPINGS ---
  function openNewMappingModal(){ document.getElementById('newMappingForm').reset(); document.getElementById('newKitTagsContainer').innerHTML=''; openModal(newMappingModal); }
  function closeNewMappingModal(){ closeModal(newMappingModal); }
  document.getElementById('addKitTagBtn').addEventListener('click',()=>{ const i=document.getElementById('newKitNameInput'); if(addKitTag(i.value.trim(), document.getElementById('newKitTagsContainer'))) i.value=''; });
    document.getElementById('newMappingForm').addEventListener('submit', e => { 
        e.preventDefault(); 
        const eng = document.getElementById('newEngineType').value.trim(); 
        const tags = document.getElementById('newKitTagsContainer').querySelectorAll('.new-kit-tag'); 
        
        if(!eng || tags.length === 0) return; 
        
        engineMappings[eng] = []; 
        
        tags.forEach(t => { 
            const name = t.dataset.kitName; 
            // Generiere neue UUID im Frontend
            const newUuid = "kit_" + Math.random().toString(36).substr(2, 9);
            
            // Objekt anlegen
            kitBoms[newUuid] = {
                id: newUuid,
                name: name,
                code: '',
                items: [],
                status: 'missing'
            };

            // Mapping auf UUID setzen
            engineMappings[eng].push(newUuid); 
        }); 
        
        renderMappingTable(); 
        closeNewMappingModal(); 
        saveDataToServer(); 
    });

    function openEditMappingModal(btn){ 
          document.getElementById('editMappingForm').reset(); 
          const r = btn.closest('tr'); 
          const eng = r.querySelector('.engine-type-cell').textContent.trim(); 
          
          document.getElementById('editMappingModalTitle').textContent = `Edit: ${eng}`; 
          document.getElementById('editEngineType').value = eng; 
          document.getElementById('originalEngineType').value = eng; 
          
          const c = document.getElementById('editKitTagsContainer'); 
          c.innerHTML = ''; 
          
          const kitIds = engineMappings[eng] || [];
          kitIds.forEach(uuid => {
              const name = kitBoms[uuid] ? kitBoms[uuid].name : uuid;
              addKitTag(name, c, deleteKitCallback);
          });
          
          deleteMappingBtn.onclick = () => deleteMapping(eng); 
          openModal(editMappingModal); 
    }
  function closeEditMappingModal(){ closeModal(editMappingModal); }
  function deleteMapping(e) { if(confirm(`Mapping "${e}" löschen?`)){ delete engineMappings[e]; saveDataToServer(); renderMappingTable(); closeEditMappingModal(); } }
  document.getElementById('addKitTagToEditBtn').addEventListener('click',()=>{ const i=document.getElementById('editKitNameInput'); if(addKitTag(i.value.trim(), document.getElementById('editKitTagsContainer'), deleteKitCallback)) i.value=''; });

  document.getElementById('editMappingForm').addEventListener('submit', e => { 
      e.preventDefault(); 
      const oEng = document.getElementById('originalEngineType').value; 
      const nEng = document.getElementById('editEngineType').value.trim(); 
      if(!nEng) return; 

      if(oEng !== nEng) {
          delete engineMappings[oEng];
          // ... (Product Type Rename Logik bleibt gleich) ...
      }

      const newKitIds = []; 
      
      document.getElementById('editKitTagsContainer').querySelectorAll('.new-kit-tag').forEach(t => { 
          const name = t.dataset.kitName; 
          
          // Checken: Existiert schon ein Kit mit diesem Namen?
          // (Wir suchen in den Values von kitBoms)
          let existingId = Object.keys(kitBoms).find(key => kitBoms[key].name === name);
          
          if (existingId) {
              newKitIds.push(existingId);
          } else {
              // Neues Kit wurde im Edit-Fenster hinzugefügt -> UUID generieren
              const newUuid = "kit_" + Math.random().toString(36).substr(2, 9);
              kitBoms[newUuid] = {
                  id: newUuid,
                  name: name,
                  code: '',
                  items: [],
                  status: 'missing'
              };
              newKitIds.push(newUuid);
          }
      }); 
      
      engineMappings[nEng] = newKitIds; 
      
      renderMappingTable(); 
      closeEditMappingModal(); 
      saveDataToServer(); 
  });
  document.getElementById('engineKitMappingTable').addEventListener('click',e=>{ if(e.target.closest('.edit-mapping-btn')) openEditMappingModal(e.target.closest('.edit-mapping-btn')); });

    // --- HARDWARE FILTER LOGIK ---
    
    function toggleMaintenanceFilter(panelId, badgeId) {
        const panel = document.getElementById(panelId);
        if (!panel) return;
        panel.classList.toggle('hidden');
    }

    function updateFilterBadge(badgeId, ids, resetBtnId) {
        const badge = document.getElementById(badgeId);
        const resetBtn = resetBtnId ? document.getElementById(resetBtnId) : null;
        if (!badge) return;
        const count = ids.filter(id => { const el = document.getElementById(id); return el && el.value !== 'all'; }).length;
        badge.textContent = count;
        if (count > 0) { badge.classList.remove('hidden'); badge.classList.add('flex'); if (resetBtn) { resetBtn.classList.remove('hidden'); resetBtn.classList.add('flex'); } }
        else { badge.classList.add('hidden'); badge.classList.remove('flex'); if (resetBtn) { resetBtn.classList.add('hidden'); resetBtn.classList.remove('flex'); } }
    }

    function resetFiltersHw() {
        ['filterHwType','filterHwProduct','filterHwStatus','filterHwLocation','filterHwStiNr'].forEach(id => { const el = document.getElementById(id); if (el) el.value = 'all'; });
        const s = document.getElementById('hardwareSearchInput'); if (s) s.value = '';
        applyHardwareFilters();
    }
    
    // Initialisiert die Dropdowns basierend auf den echten Daten
    function initHardwareFilters() {
        const types = new Set();
        const products = new Set();
        const locations = new Set();
        const stiNrs = new Set();

        // Durch Inventar iterieren und Werte sammeln
        Object.values(hardwareInventory).forEach(part => {
            if (part.type) types.add(part.type.trim());

            // Product Type (kann Array oder String sein)
            if (Array.isArray(part.productType)) {
                part.productType.forEach(p => { if(p) products.add(p.trim()); });
            } else if (part.productType) {
                products.add(part.productType.trim());
            }

            // Locations aus Serialnummern sammeln
            if (part.serials) {
                part.serials.forEach(s => {
                    if (s.location && s.location.trim()) locations.add(s.location.trim());
                });
            }

            // STI-Nr.
            if (part.stiNr && part.stiNr.trim()) stiNrs.add(part.stiNr.trim());
        });

        const fillSelect = (id, items) => {
            const el = document.getElementById(id);
            if(!el) return;
            // Reset auf Option "Alle"
            el.innerHTML = el.options[0].outerHTML;

            Array.from(items).sort().forEach(item => {
                const opt = document.createElement('option');
                opt.value = item;
                opt.textContent = item;
                el.appendChild(opt);
            });
        };

        fillSelect('filterHwType', types);
        fillSelect('filterHwProduct', products);
        fillSelect('filterHwLocation', locations);
        fillSelect('filterHwStiNr', stiNrs);
    }
    
    // Die eigentliche Filter-Funktion
    function applyHardwareFilters() {
        const term = document.getElementById('hardwareSearchInput').value.toLowerCase();
        // Reset: Alles zuklappen vor der Suche
        document.querySelectorAll('.sn-row').forEach(r => r.classList.remove('is-visible'));
        document.querySelectorAll('.toggle-sn-rows').forEach(btn => btn.classList.remove('is-open'));
        areAllRowsExpanded = false; // Globalen State resetten
        const expandIcon = document.getElementById('expandAllBtn')?.querySelector('.material-icons-outlined');
        if(expandIcon) expandIcon.textContent = 'unfold_more';
        
        // IDs der Chips
        const filters = {
            type: document.getElementById('filterHwType'),
            prod: document.getElementById('filterHwProduct'),
            status: document.getElementById('filterHwStatus'),
            loc: document.getElementById('filterHwLocation'),
            stiNr: document.getElementById('filterHwStiNr')
        };

        // Werte holen (Safeguard falls Elemente beim Laden kurz fehlen)
        const typeVal = filters.type ? filters.type.value : 'all';
        const prodVal = filters.prod ? filters.prod.value : 'all';
        const statusVal = filters.status ? filters.status.value : 'all';
        const locVal = filters.loc ? filters.loc.value : 'all';
        const stiNrVal = filters.stiNr ? filters.stiNr.value : 'all';
    
        // 1. Visual Feedback für Chips (Blau färben wenn aktiv)
        Object.values(filters).forEach(selectEl => {
            if (!selectEl) return;
            if (selectEl.value !== 'all') {
                // Aktiv Style: Blau & Fett
                selectEl.classList.remove('bg-white', 'text-slate-600', 'border-slate-300');
                selectEl.classList.add('bg-blue-50', 'text-blue-700', 'border-blue-200', 'font-bold');
            } else {
                // Inaktiv Style: Grau & Standard
                selectEl.classList.add('bg-white', 'text-slate-600', 'border-slate-300');
                selectEl.classList.remove('bg-blue-50', 'text-blue-700', 'border-blue-200', 'font-bold');
            }
        });
        updateFilterBadge('filterBadgeHw', ['filterHwType','filterHwProduct','filterHwStatus','filterHwLocation','filterHwStiNr'], 'filterResetHw');
    
        let visiblePnCount = 0;
        let visibleSnCount = 0;
        let totalPnCount = 0;
    
        // 2. Durch die Tabelle iterieren
        document.querySelectorAll('#hardwareInventoryTbody tr.pn-row').forEach(row => {
            const pn = row.dataset.pn;
            const part = hardwareInventory[pn];
            totalPnCount++;
    
            if (!part) return;
    
            let matches = true;
    
            // A. Text Suche (P/N, Name, Desc, Type, S/Ns)
            if (term) {
                const serialsString = part.serials.map(s => s.sn).join(' ');
                const textContent = `${pn} ${part.name} ${part.description || ''} ${part.type || ''} ${serialsString}`.toLowerCase();
                if (!textContent.includes(term)) matches = false;
            }
    
            // B. Hardware Type
            if (matches && typeVal !== 'all') {
                if (part.type !== typeVal) matches = false;
            }
    
            // C. Product Type
            if (matches && prodVal !== 'all') {
                const pTypes = Array.isArray(part.productType) ? part.productType : [part.productType];
                if (!pTypes.includes(prodVal)) matches = false;
            }
    
            // D. Status (Prüft, ob mindestens eine S/N diesen Status hat)
            if (matches && statusVal !== 'all') {
                if (!part.serials.some(s => s.status === statusVal)) matches = false;
            }
    
            // E. Location (Prüft, ob mindestens eine S/N an diesem Ort ist)
            if (matches && locVal !== 'all') {
                if (!part.serials.some(s => s.location === locVal)) matches = false;
            }

            // F. STI-Nr.
            if (matches && stiNrVal !== 'all') {
                if ((part.stiNr || '').trim() !== stiNrVal) matches = false;
            }
    
            // 3. Ergebnis anzeigen/verstecken
            if (matches) {
                row.style.display = '';
                visiblePnCount++;
                
                // Intelligente S/N Zählung für die Statistik
                // Wenn wir nach Status oder Location filtern, zählen wir nur die S/Ns, die auch wirklich passen.
                let matchingSns = 0;
                if (statusVal === 'all' && locVal === 'all') {
                    matchingSns = part.serials.length;
                } else {
                    matchingSns = part.serials.filter(s => 
                        (statusVal === 'all' || s.status === statusVal) &&
                        (locVal === 'all' || s.location === locVal)
                    ).length;
                }
                visibleSnCount += matchingSns;
    
                // Optional: Wenn der User explizit filtert, könnte man die Unterzeilen (S/Ns)
                // hier auch filtern/highlighten, aber das ist komplexer.
                // Aktuell zeigen wir die Hauptzeile (P/N), wenn IRGENDWAS passt.
                
            } else {
                row.style.display = 'none';
            }
        });
    
        // 4. Counter Update
        const counterEl = document.getElementById('hwTableCount');
        if (counterEl) {
            const isFilterActive = (typeVal !== 'all' || prodVal !== 'all' || statusVal !== 'all' || locVal !== 'all' || stiNrVal !== 'all');
            
            // Zeige Counter, wenn gefiltert wird ODER gesucht wird ODER nicht alle Ergebnisse sichtbar sind
            if (visiblePnCount < totalPnCount || term || isFilterActive) {
                counterEl.textContent = `${visiblePnCount} P/N (${visibleSnCount} S/N)`;
                counterEl.classList.remove('hidden');
                
                // Roter Hintergrund, wenn 0 Ergebnisse
                if (visiblePnCount === 0) {
                    counterEl.classList.remove('bg-slate-100', 'text-slate-500');
                    counterEl.classList.add('bg-red-100', 'text-red-600');
                } else {
                    counterEl.classList.add('bg-slate-100', 'text-slate-500');
                    counterEl.classList.remove('bg-red-100', 'text-red-600');
                }
            } else {
                counterEl.classList.add('hidden');
            }
        }
    }
  document.getElementById('hardwareSearchInput').addEventListener('input', applyHardwareFilters);

  // Deep-Link: ?hwid=<uuid> → exakte sn-row aufklappen, scrollen, highlighten
  (function handleHwDeepLink() {
      const hwid = new URLSearchParams(window.location.search).get('hwid');
      if (!hwid) return;
      window.history.replaceState({}, '', '/test_equipment');
      setTimeout(() => {
          const snRow = document.querySelector(`tr.sn-row[data-hwid="${hwid}"]`);
          if (!snRow) return;
          // Übergeordnete pn-row aufklappen
          const pn = snRow.dataset.parentPn;
          const pnRow = document.querySelector(`tr.pn-row[data-pn="${pn}"]`);
          if (pnRow) {
              const toggleBtn = pnRow.querySelector('.toggle-sn-rows');
              if (toggleBtn && !toggleBtn.classList.contains('is-open')) toggleBtn.click();
          }
          // Scrollen & kurzen Highlight-Flash
          snRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
          snRow.style.transition = 'background-color 0.4s ease';
          snRow.style.backgroundColor = '#dbeafe';
          setTimeout(() => { snRow.style.backgroundColor = ''; }, 2000);
      }, 300);
  })();
  window.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal(bomModal); closeModal(partNumberModal); closeModal(newMappingModal); closeModal(editMappingModal); closeModal(trolleyModal); closeModal(importModal); } });

  // --- KIT LOGIC ---
    function getKitName(id) {
        if (!id) return '-';
        // Fall 1: Es ist eine UUID und wir finden sie in den BOMs
        if (kitBoms[id] && kitBoms[id].name) {
            return kitBoms[id].name;
        }
        // Fall 2: Fallback (falls Migration noch nicht lief oder es ein alter String ist)
        return id; 
    }
    function getKitStatusClass(s) { return s === 'ready' ? 'bg-kit-ready' : s === 'partial' ? 'bg-kit-partial' : 'bg-kit-missing'; }
    function updateAndReassignKits() {
        // Wir berechnen NICHTS mehr selbst. 
        // Wir vertrauen darauf, dass 'hardwareInventory' (kitId) und 'kitBoms' (status) 
        // vom Server korrekt befüllt wurden.
        
        renderHardwareTable();
        updateAllKitStatuses(); // Zeigt nur die Farben an
        applyHardwareFilters();
        renderMappingTable();
    }
    
    function updateAllKitStatuses() {
        // Färbt die Punkte in der Mapping-Tabelle basierend auf dem Server-Status
        document.querySelectorAll('.kit-tag').forEach(t => { 
            const kitId = t.dataset.kitId; // Wir speichern die ID jetzt im Dataset
            const bomData = kitBoms[kitId]; 
            
            if(bomData && bomData.status) { 
                t.querySelector('.kit-status-dot').className = `kit-status-dot ${getKitStatusClass(bomData.status)}`; 
            } 
        });
    }

// --- SELECTION LOGIC ---
  function togglePnSelection(pn) {
      if (selectedPartNumbers.has(pn)) selectedPartNumbers.delete(pn);
      else selectedPartNumbers.add(pn);
      updateBulkUI();
  }

  function handleSelectAllHardware(checkbox) {
      const isChecked = checkbox.checked;
      const visibleRows = document.querySelectorAll('#hardwareInventoryTbody tr.pn-row:not([style*="display: none"])');
      visibleRows.forEach(row => {
          const pn = row.dataset.pn;
          if (isChecked) selectedPartNumbers.add(pn);
          else selectedPartNumbers.delete(pn);
          const cb = row.querySelector('.pn-checkbox');
          if (cb) cb.checked = isChecked;
      });
      updateBulkUI();
  }

  function updateBulkUI() {
      const btn = document.getElementById('bulkEditBtn');
      const countSpan = document.getElementById('bulkEditCount');
      const count = selectedPartNumbers.size;

      if (count > 0) {
          btn.classList.remove('hidden');
          countSpan.textContent = `${count} Selected`;
      } else {
          btn.classList.add('hidden');
      }
  }

  // --- BULK EDIT LOGIC ---

  // Wiederverwendbare Tag-Logik für Bulk Modal (Kopie der Logik vom PartModal, aber auf Bulk-Elemente gemappt)
  function renderBulkProductTags() {
      const container = document.getElementById('bulkProductTags');
      const input = document.getElementById('bulkProductInput');
      // Bestehende Chips entfernen (außer Input)
      Array.from(container.children).forEach(c => { if(c !== input) c.remove(); });

      currentBulkProductTags.forEach(tag => {
          const chip = document.createElement('span');
          chip.className = 'tag-chip';
          chip.innerHTML = `${tag}<span class="tag-chip-remove" onclick="removeBulkProductTag('${tag}')">×</span>`;
          container.insertBefore(chip, input);
      });
      input.value = '';
  }

  function addBulkProductTag(tag) {
      if (tag && !currentBulkProductTags.includes(tag)) {
          currentBulkProductTags.push(tag);
          renderBulkProductTags();
      }
      document.getElementById('bulkProductDropdown').classList.add('hidden');
  }

  window.removeBulkProductTag = function(tag) {
      currentBulkProductTags = currentBulkProductTags.filter(t => t !== tag);
      renderBulkProductTags();
  }

  function setupBulkTagInput() {
      // ... (Analog zu setupProductTagInput, nur mit IDs 'bulkProductInput' und 'bulkProductDropdown') ...
      // (Du kannst hier exakt den Code von setupProductTagInput kopieren und die IDs anpassen)
      const input = document.getElementById('bulkProductInput');
      const dropdown = document.getElementById('bulkProductDropdown');
      const availableTypes = new Set(['N/A', 'Facility']);
      document.querySelectorAll('#engineKitMappingTable .engine-type-cell').forEach(cell => availableTypes.add(cell.textContent.trim()));

      const newInput = input.cloneNode(true);
      input.parentNode.replaceChild(newInput, input);

      newInput.addEventListener('input', () => {
          const val = newInput.value.toLowerCase();
          dropdown.innerHTML = '';
          const filtered = Array.from(availableTypes).filter(t => t.toLowerCase().includes(val) && !currentBulkProductTags.includes(t));
          if (filtered.length > 0) {
              filtered.forEach(t => {
                  const div = document.createElement('div');
                  div.className = 'tag-dropdown-item';
                  div.textContent = t;
                  div.onclick = () => addBulkProductTag(t);
                  dropdown.appendChild(div);
              });
              dropdown.classList.remove('hidden');
          } else dropdown.classList.add('hidden');
      });
      document.addEventListener('click', (e) => { if (!newInput.contains(e.target) && !dropdown.contains(e.target)) dropdown.classList.add('hidden'); });
      newInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addBulkProductTag(newInput.value.trim()); } });
  }

  function openBulkEditModal() {
      document.getElementById('bulkEditCountLabel').textContent = selectedPartNumbers.size;
      document.getElementById('bulkHardwareType').value = '';
      currentBulkProductTags = [];
      renderBulkProductTags();
      setupBulkTagInput();
      openModal(bulkEditModal);
  }

  function closeBulkEditModal() { closeModal(bulkEditModal); }

  function submitBulkEdit() {
      const newHwType = document.getElementById('bulkHardwareType').value.trim();
      const newProdTypes = currentBulkProductTags; // Array

      if (!newHwType && newProdTypes.length === 0) {
          closeBulkEditModal();
          return;
      }

      if (!confirm(`Änderungen auf ${selectedPartNumbers.size} Teile anwenden?`)) return;

      selectedPartNumbers.forEach(pn => {
          if (hardwareInventory[pn]) {
              if (newHwType) hardwareInventory[pn].type = newHwType;
              if (newProdTypes.length > 0) hardwareInventory[pn].productType = [...newProdTypes];
          }
      });

      saveDataToServer();
      renderHardwareTable();

      // Reset Selection
      selectedPartNumbers.clear();
      document.getElementById('selectAllHardware').checked = false;
      updateBulkUI();

      closeBulkEditModal();
  }

  // ─── BULK IMPORT LOGIC ──────────────────────────────────────────────────────

  function openImportModal() {
      importParsedRows = [];
      document.getElementById('importFileInput').value = '';
      document.getElementById('importPreviewSection').classList.add('hidden');
      document.getElementById('importConfirmBtn').disabled = true;
      document.getElementById('importConfirmLabel').textContent = 'Importieren';
      document.getElementById('importErrorMsg').classList.add('hidden');
      openModal(importModal);
  }

  function closeImportModal() { closeModal(importModal); }

  function downloadImportTemplate() {
      const header = 'part_number,name,type,product_type,description,sti_nr,serial_number,location,status,last_check';
      const example = 'PWC12345,Adapter Short,SPTE,PW814,Optionale Beschreibung,4711,SN-0001,Lager A,Available,2024-01-15';
      const blob = new Blob([header + '\\n' + example + '\\n'], { type: 'text/csv;charset=utf-8;' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url; a.download = 'Hardware_Import_Vorlage.csv';
      document.body.appendChild(a); a.click();
      document.body.removeChild(a); URL.revokeObjectURL(url);
  }

  function parseCSV(text) {
      // Handles quoted fields (commas inside quotes), CRLF and LF
      const lines = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n').split('\\n');
      const result = [];
      for (const line of lines) {
          if (!line.trim()) continue;
          const fields = [];
          let cur = '', inQ = false;
          for (let i = 0; i < line.length; i++) {
              const ch = line[i];
              if (ch === '"') {
                  if (inQ && line[i+1] === '"') { cur += '"'; i++; }
                  else inQ = !inQ;
              } else if (ch === ',' && !inQ) {
                  fields.push(cur.trim()); cur = '';
              } else {
                  cur += ch;
              }
          }
          fields.push(cur.trim());
          result.push(fields);
      }
      return result;
  }

  function handleImportFile(input) {
      const file = input.files[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = (e) => {
          const errEl = document.getElementById('importErrorMsg');
          errEl.classList.add('hidden');
          try {
              const rows = parseCSV(e.target.result);
              if (rows.length < 2) {
                  errEl.textContent = 'Die Datei ist leer oder enthält nur die Kopfzeile.';
                  errEl.classList.remove('hidden');
                  document.getElementById('importPreviewSection').classList.remove('hidden');
                  return;
              }
              const header = rows[0].map(h => h.toLowerCase().replace(/\s+/g,'_'));
              const idx = (name) => header.indexOf(name);
              const required = ['part_number', 'name', 'type', 'serial_number'];
              const missing = required.filter(r => idx(r) === -1);
              if (missing.length > 0) {
                  errEl.textContent = `Pflicht-Spalten fehlen: ${missing.join(', ')}`;
                  errEl.classList.remove('hidden');
                  document.getElementById('importPreviewSection').classList.remove('hidden');
                  return;
              }
              importParsedRows = [];
              for (let i = 1; i < rows.length; i++) {
                  const r = rows[i];
                  const pn = (r[idx('part_number')] || '').trim();
                  const sn = (r[idx('serial_number')] || '').trim();
                  if (!pn || !sn) continue; // Überspringe Zeilen ohne P/N oder S/N
                  const row = {
                      pn, sn,
                      name: (r[idx('name')] || '').trim(),
                      type: (r[idx('type')] || '').trim(),
                      product_type: (r[idx('product_type')] || '').trim(),
                      description: (r[idx('description')] || '').trim(),
                      location: (r[idx('location')] || '').trim(),
                      status: (r[idx('status')] || 'Available').trim(),
                      last_check: (r[idx('last_check')] || '').trim(),
                      sti_nr: (r[idx('sti_nr')] || '').trim(),
                  };
                  // Duplikat-Erkennung
                  if (hardwareInventory[pn]) {
                      const snExists = hardwareInventory[pn].serials.some(s => s.sn === sn);
                      row._action = snExists ? 'skip' : 'merge';
                  } else {
                      // Prüfen ob P/N bereits in dieser Import-Datei neu angelegt wurde
                      row._action = importParsedRows.some(prev => prev.pn === pn) ? 'merge' : 'create';
                  }
                  importParsedRows.push(row);
              }
              renderImportPreview();
          } catch(err) {
              errEl.textContent = 'Fehler beim Lesen der Datei: ' + err.message;
              errEl.classList.remove('hidden');
              document.getElementById('importPreviewSection').classList.remove('hidden');
          }
      };
      reader.readAsText(file, 'UTF-8');
  }

  function renderImportPreview() {
      const tbody = document.getElementById('importPreviewTbody');
      tbody.innerHTML = '';
      let creates = 0, merges = 0, skips = 0;
      const validStatuses = ['Available', 'Maintenance', 'In Order', 'Decommissioned'];

      importParsedRows.forEach(row => {
          const tr = document.createElement('tr');
          const safeStatus = validStatuses.includes(row.status) ? row.status : 'Available';
          if (row._action === 'skip') {
              skips++;
              tr.className = 'opacity-50';
              tr.innerHTML = `
                  <td class="px-3 py-2"><span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-slate-100 text-slate-500 border border-slate-200"><span class="material-icons-outlined text-[10px]">block</span>Skip</span></td>
                  <td class="px-3 py-2 font-mono text-slate-400">${row.pn}</td>
                  <td class="px-3 py-2 text-slate-400" colspan="5">S/N bereits vorhanden</td>
                  <td class="px-3 py-2 font-mono text-slate-400">${row.sn}</td>`;
          } else if (row._action === 'merge') {
              merges++;
              tr.className = 'bg-yellow-50';
              tr.innerHTML = `
                  <td class="px-3 py-2"><span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-yellow-100 text-yellow-700 border border-yellow-200"><span class="material-icons-outlined text-[10px]">merge</span>Merge</span></td>
                  <td class="px-3 py-2 font-mono font-medium text-slate-700">${row.pn}</td>
                  <td class="px-3 py-2 text-slate-500 italic text-[10px]">bestehend</td>
                  <td class="px-3 py-2 text-slate-500"></td>
                  <td class="px-3 py-2 text-slate-500"></td>
                  <td class="px-3 py-2 font-mono text-slate-700">${row.sn}</td>
                  <td class="px-3 py-2 text-slate-600">${row.location}</td>
                  <td class="px-3 py-2">${safeStatus}</td>`;
          } else {
              creates++;
              tr.className = 'bg-green-50';
              tr.innerHTML = `
                  <td class="px-3 py-2"><span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-green-100 text-green-700 border border-green-200"><span class="material-icons-outlined text-[10px]">add_circle</span>Neu</span></td>
                  <td class="px-3 py-2 font-mono font-medium text-slate-800">${row.pn}</td>
                  <td class="px-3 py-2 text-slate-700">${row.name}</td>
                  <td class="px-3 py-2 text-slate-600">${row.type}</td>
                  <td class="px-3 py-2 text-slate-600">${row.product_type}</td>
                  <td class="px-3 py-2 font-mono text-slate-700">${row.sn}</td>
                  <td class="px-3 py-2 text-slate-600">${row.location}</td>
                  <td class="px-3 py-2">${safeStatus}</td>`;
          }
          tbody.appendChild(tr);
      });

      // Stats Badges
      const statsEl = document.getElementById('importStats');
      statsEl.innerHTML = `
          <span class="px-2 py-0.5 rounded-full bg-green-100 text-green-700 font-semibold">${creates} neu</span>
          <span class="px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-700 font-semibold">${merges} merge</span>
          <span class="px-2 py-0.5 rounded-full bg-slate-100 text-slate-500 font-semibold">${skips} skip</span>`;

      document.getElementById('importPreviewSection').classList.remove('hidden');

      const actionable = creates + merges;
      const btn = document.getElementById('importConfirmBtn');
      btn.disabled = actionable === 0;
      document.getElementById('importConfirmLabel').textContent = actionable > 0
          ? `${actionable} Eintr${actionable === 1 ? 'ag' : 'äge'} importieren`
          : 'Nichts zu importieren';
  }

  async function executeImport() {
      const validStatuses = ['Available', 'Maintenance', 'In Order', 'Decommissioned'];
      let importedCount = 0;

      importParsedRows.forEach(row => {
          if (row._action === 'skip') return;

          const safeStatus = validStatuses.includes(row.status) ? row.status : 'Available';
          const serialEntry = {
              sn: row.sn,
              location: row.location || '',
              status: safeStatus,
              lastCheck: row.last_check || '',
              maintenanceId: 'N/A',
              documents: [],
              maintenanceLinks: [],
              statusSummary: { light: 'NONE', nextDue: 'N/A', openIssues: 0, issueList: [] },
              kitId: 'N/A'
          };

          if (row._action === 'create' && !hardwareInventory[row.pn]) {
              // Neues Item anlegen
              const prodTypes = row.product_type ? row.product_type.split(';').map(t => t.trim()).filter(Boolean) : [];
              hardwareInventory[row.pn] = {
                  name: row.name,
                  type: row.type,
                  productType: prodTypes,
                  description: row.description || '',
                  stiNr: row.sti_nr || '',
                  serials: [serialEntry]
              };
          } else if (row._action === 'create' && hardwareInventory[row.pn]) {
              // P/N wurde durch eine frühere Zeile in diesem Import neu angelegt → Serial anhängen
              const snExists = hardwareInventory[row.pn].serials.some(s => s.sn === row.sn);
              if (!snExists) hardwareInventory[row.pn].serials.push(serialEntry);
          } else if (row._action === 'merge') {
              const snExists = hardwareInventory[row.pn].serials.some(s => s.sn === row.sn);
              if (!snExists) hardwareInventory[row.pn].serials.push(serialEntry);
              // STI-Nr. nur setzen wenn bisher leer
              if (!hardwareInventory[row.pn].stiNr && row.sti_nr) {
                  hardwareInventory[row.pn].stiNr = row.sti_nr;
              }
          }
          importedCount++;
      });

      closeImportModal();
      initHardwareFilters();       // Filter-Dropdowns mit neuen Typen/Locations aktualisieren
      updateAndReassignKits();     // Tabelle neu rendern + Filter anwenden
      await saveDataToServer();    // Persistieren (ensure_hardware_ids ergänzt UUIDs serverseitig)
  }

  // Fixed-position popover positioning (escapes overflow-x-auto clipping)
  (function() {
      let hideTimer = null;
      let activePopover = null;

      function showPopover(wrapper) {
          clearTimeout(hideTimer);
          hideTimer = null;
          const popover = wrapper.querySelector('.maint-popover-content');
          if (!popover) return;
          if (activePopover && activePopover !== popover) {
              activePopover.classList.remove('is-visible');
          }
          activePopover = popover;
          const rect = wrapper.getBoundingClientRect();
          const popWidth = popover.offsetWidth || parseInt(popover.style.minWidth) || 280;
          let left = rect.left + rect.width / 2 - popWidth / 2;
          left = Math.max(8, Math.min(left, window.innerWidth - popWidth - 8));
          popover.style.left = left + 'px';
          popover.style.top = rect.top + 'px';
          popover.style.transform = 'translateY(-100%)';
          popover.classList.add('is-visible');
      }

      function scheduleHide() {
          clearTimeout(hideTimer);
          hideTimer = setTimeout(() => {
              if (activePopover) { activePopover.classList.remove('is-visible'); activePopover = null; }
              hideTimer = null;
          }, 150);
      }

      document.addEventListener('mouseover', e => {
          const wrapper = e.target.closest('.maint-popover-wrapper');
          if (wrapper) { showPopover(wrapper); return; }
          if (e.target.closest('.maint-popover-content')) { clearTimeout(hideTimer); hideTimer = null; }
      });

      document.addEventListener('mouseout', e => {
          const inWrapper = e.target.closest('.maint-popover-wrapper');
          const inPop = e.target.closest('.maint-popover-content');
          if (!inWrapper && !inPop) return;
          const toWrapper = e.relatedTarget && e.relatedTarget.closest('.maint-popover-wrapper');
          const toPop = e.relatedTarget && e.relatedTarget.closest('.maint-popover-content');
          if (!toWrapper && !toPop) scheduleHide();
      });
  })();

  // ── VOLLSTÄNDIGKEITS-PRÜFPROTOKOLL ──────────────────────────────────────

  function _protocolWeekDates(offset) {
      const now = new Date();
      const day = now.getDay();
      const diffToMon = day === 0 ? -6 : 1 - day;
      const mon = new Date(now);
      mon.setDate(now.getDate() + diffToMon + offset * 7);
      return Array.from({length: 5}, (_, i) => { const d = new Date(mon); d.setDate(mon.getDate() + i); return d; });
  }

  function _isoDate(d) { return d.toISOString().split('T')[0]; }

  function _kwNumber(d) {
      const dt = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
      dt.setUTCDate(dt.getUTCDate() + 4 - (dt.getUTCDay() || 7));
      const y = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
      return Math.ceil((((dt - y) / 86400000) + 1) / 7);
  }

  async function openCompletenessProtocolModal() {
      protocolWeekOffset = 0;
      showProtocolHeatmapView();
      openModal(document.getElementById('completenessProtocolModal'));
      try {
          const res = await fetch('/api/completeness_log');
          protocolLog = res.ok ? await res.json() : [];
      } catch(e) { protocolLog = []; }
      renderProtocolHeatmap();
      updateWeeklyKPI();
  }

  function closeCompletenessProtocolModal() {
      closeModal(document.getElementById('completenessProtocolModal'));
  }

  function showProtocolHeatmapView() {
      document.getElementById('protocolHeatmapView').classList.remove('hidden');
      document.getElementById('protocolSettingsView').classList.add('hidden');
      document.getElementById('btnProtocolBack').classList.add('hidden');
      document.getElementById('btnProtocolSettings').classList.remove('hidden');
      document.getElementById('protocolModalTitle').textContent = 'Vollständigkeits-Prüfprotokoll';
      document.getElementById('protocolModalSubtitle').textContent = 'Wochenübersicht Mo–Fr';
  }

  function showProtocolSettingsView() {
      document.getElementById('protocolHeatmapView').classList.add('hidden');
      document.getElementById('protocolSettingsView').classList.remove('hidden');
      document.getElementById('btnProtocolBack').classList.remove('hidden');
      document.getElementById('btnProtocolSettings').classList.add('hidden');
      document.getElementById('protocolModalTitle').textContent = 'Einstellungen';
      document.getElementById('protocolModalSubtitle').textContent = 'Container & Prüfhäufigkeit';
      renderProtocolSettings();
  }

  function navigateProtocolWeek(dir) {
      protocolWeekOffset += dir;
      renderProtocolHeatmap();
      document.getElementById('protocolDetailPanel').classList.add('hidden');
  }

  function renderProtocolHeatmap() {
      const days = _protocolWeekDates(protocolWeekOffset);
      const today = _isoDate(new Date());
      const dayIsos = days.map(_isoDate);
      const kw = _kwNumber(days[0]);
      document.getElementById('protocolWeekLabel').textContent =
          `KW ${kw} · ${days[0].getDate()}.${String(days[0].getMonth()+1).padStart(2,'0')}. – ${days[4].getDate()}.${String(days[4].getMonth()+1).padStart(2,'0')}.${days[4].getFullYear()}`;

      const cfg = completenessConfig.containers || {};
      const active = Object.entries(cfg).filter(([_, v]) => v.requires_check);
      const grid = document.getElementById('protocolHeatmapGrid');
      const emptyEl = document.getElementById('protocolHeatmapEmpty');

      if (active.length === 0) { grid.innerHTML = ''; emptyEl.classList.remove('hidden'); return; }
      emptyEl.classList.add('hidden');

      const DAY_NAMES = ['Mo', 'Di', 'Mi', 'Do', 'Fr'];
      // lookup: container → date → entries[]
      const lkp = {};
      protocolLog.forEach(e => {
          if (!lkp[e.container]) lkp[e.container] = {};
          if (!lkp[e.container][e.date]) lkp[e.container][e.date] = [];
          lkp[e.container][e.date].push(e);
      });

      const esc = s => String(s||'').replace(/"/g,'&quot;');
      let html = `<div class="grid gap-x-2 gap-y-0 mb-1" style="grid-template-columns:9rem repeat(5,1fr)">
          <div></div>`;
      DAY_NAMES.forEach((n, i) => {
          const isToday = dayIsos[i] === today;
          html += `<div class="text-center pb-2">
              <div class="text-xs font-semibold ${isToday ? 'text-blue-600' : 'text-slate-500'}">${n}</div>
              <div class="text-xs text-slate-400">${days[i].getDate()}.${String(days[i].getMonth()+1).padStart(2,'0')}.</div>
          </div>`;
      });
      html += `</div>`;

      active.sort(([a],[b]) => a.localeCompare(b)).forEach(([name, vcfg]) => {
          const freq = vcfg.frequency || '2x_daily';
          const containerEntries = protocolLog.filter(e => e.container === name);
          const weekDone = freq === '1x_weekly' && dayIsos.some(iso => (lkp[name]&&lkp[name][iso]||[]).length > 0);

          html += `<div class="grid gap-x-2 gap-y-0 mb-2 items-center" style="grid-template-columns:9rem repeat(5,1fr)">`;
          html += `<div class="text-sm font-medium text-slate-700 truncate pr-2 py-1" title="${esc(name)}">${esc(name)}</div>`;

          dayIsos.forEach((iso, i) => {
              const isFuture = iso > today;
              const isBeforeStart = completenessConfig.start_date && iso < completenessConfig.start_date;
              const entries = (lkp[name]&&lkp[name][iso]) || [];
              let cls, inner, clickable = false;

              if (freq === '1x_weekly') {
                  if (weekDone) { cls = 'bg-green-50 border-green-300'; inner = '<span class="text-green-500 text-xs">✓</span>'; clickable = true; }
                  else if (isFuture || isBeforeStart) { cls = 'bg-slate-50 border-slate-200'; inner = ''; }
                  else { cls = 'bg-red-50 border-red-300'; inner = ''; }
              } else if (freq === '2x_daily') {
                  const hasF = entries.some(e => e.shift==='F');
                  const hasS = entries.some(e => e.shift==='S');
                  const hasW = entries.some(e => e.status==='unvollständig');
                  if (isFuture || isBeforeStart) { cls = 'bg-slate-50 border-slate-200'; inner = `<span class="text-slate-300 text-xs leading-none">F<br>S</span>`; }
                  else if (hasF && hasS) { cls = hasW ? 'bg-orange-50 border-orange-300' : 'bg-green-50 border-green-300'; inner = `<span class="${hasW?'text-orange-500':'text-green-500'} text-xs leading-none">✓<br>✓</span>`; clickable=true; }
                  else if (hasF || hasS) { cls = 'bg-orange-50 border-orange-300'; inner = `<span class="text-orange-500 text-xs leading-none">${hasF?'✓':'–'}<br>${hasS?'✓':'–'}</span>`; clickable=true; }
                  else { cls = 'bg-red-50 border-red-300'; inner = `<span class="text-red-200 text-xs leading-none">–<br>–</span>`; }
              } else { // 1x_daily
                  const hasAny = entries.length > 0;
                  const hasW = entries.some(e => e.status==='unvollständig');
                  if (isFuture || isBeforeStart) { cls = 'bg-slate-50 border-slate-200'; inner = ''; }
                  else if (hasAny) { cls = hasW ? 'bg-orange-50 border-orange-300' : 'bg-green-50 border-green-300'; inner = `<span class="${hasW?'text-orange-500':'text-green-500'} text-xs">✓</span>`; clickable=true; }
                  else { cls = 'bg-red-50 border-red-300'; inner = ''; }
              }
              const onclick = clickable ? `onclick="showProtocolDetail('${esc(name)}','${iso}')"` : '';
              html += `<div class="h-10 rounded-lg border ${cls} flex items-center justify-center text-center ${clickable?'cursor-pointer hover:opacity-80':''} transition-opacity" ${onclick}>${inner}</div>`;
          });
          html += `</div>`;
      });

      grid.innerHTML = html;
  }

  function showProtocolDetail(containerName, dateIso) {
      const entries = protocolLog.filter(e => e.container === containerName && e.date === dateIso);
      if (!entries.length) return;
      const panel = document.getElementById('protocolDetailPanel');
      const dateStr = dateIso.split('-').reverse().join('.');
      document.getElementById('protocolDetailTitle').textContent = `${containerName} · ${dateStr}`;
      document.getElementById('protocolDetailList').innerHTML = entries.map(e => {
          const shift = e.shift === 'F' ? 'Frühschicht' : 'Spätschicht';
          const ok = e.status === 'vollständig';
          const badge = ok ? '<span class="px-2 py-0.5 rounded text-xs bg-green-100 text-green-700">✓ vollständig</span>'
                           : '<span class="px-2 py-0.5 rounded text-xs bg-orange-100 text-orange-700">⚠ unvollständig</span>';
          const missing = (!ok && e.missingItems && e.missingItems.length)
              ? `<span class="text-xs text-slate-400 ml-1">Fehlend: ${e.missingItems.join(', ')}</span>` : '';
          return `<div class="flex items-center gap-3 py-1.5 border-b border-slate-100 last:border-0">
              <span class="text-xs text-slate-500 w-20 flex-shrink-0">${shift}</span>
              <span class="text-xs text-slate-700 flex-grow">${e.checkedBy}</span>
              ${badge}${missing}
          </div>`;
      }).join('');
      panel.classList.remove('hidden');
  }

  function renderProtocolSettings() {
      const startInput = document.getElementById('protocolStartDate');
      if (startInput) startInput.value = completenessConfig.start_date || '';
      const cfg = completenessConfig.containers || {};
      const all = [
          ...Object.keys(trolleys).map(n => ({name:n, type:'Rüstwagen', sub:(trolleys[n]||{}).engineType||''})),
          ...Object.keys(storageUnits).map(n => ({name:n, type:'Lagerort', sub:(storageUnits[n]||{}).engineType||''}))
      ].sort((a,b) => a.name.localeCompare(b.name));

      const FREQS = [
          {v:'2x_daily', l:'2× täglich (F + S)'},
          {v:'1x_daily', l:'1× täglich'},
          {v:'1x_weekly', l:'1× wöchentlich'},
      ];
      const esc = s => String(s||'').replace(/"/g,'&quot;');

      document.getElementById('protocolSettingsList').innerHTML = all.map(c => {
          const ccfg = cfg[c.name] || {requires_check:false, frequency:'2x_daily'};
          const freqOpts = FREQS.map(o => `<option value="${o.v}"${ccfg.frequency===o.v?' selected':''}>${o.l}</option>`).join('');
          return `<div class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-slate-50 border border-transparent hover:border-slate-200 transition-colors">
              <input type="checkbox" class="protocol-check-toggle w-4 h-4 rounded text-blue-600 cursor-pointer" data-container="${esc(c.name)}" ${ccfg.requires_check?'checked':''}>
              <div class="flex-grow min-w-0">
                  <span class="text-sm font-medium text-slate-800">${esc(c.name)}</span>
                  <span class="text-xs text-slate-400 ml-2">${c.type}${c.sub?' · '+c.sub:''}</span>
              </div>
              <select class="protocol-freq-select border border-slate-200 rounded-md text-xs px-2 py-1.5 bg-white text-slate-600 focus:ring-1 focus:ring-blue-500" data-container="${esc(c.name)}">${freqOpts}</select>
          </div>`;
      }).join('');
  }

  async function saveCompletenessConfig() {
      const startInput = document.getElementById('protocolStartDate');
      const newCfg = {containers:{}, start_date: startInput ? startInput.value : ''};
      document.querySelectorAll('.protocol-check-toggle').forEach(cb => {
          const n = cb.dataset.container;
          const sel = document.querySelector(`.protocol-freq-select[data-container="${n}"]`);
          newCfg.containers[n] = {requires_check: cb.checked, frequency: sel ? sel.value : '2x_daily'};
      });
      try {
          const res = await fetch('/api/completeness_config', {
              method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(newCfg)
          });
          if (!res.ok) { alert('Fehler beim Speichern.'); return; }
          completenessConfig = newCfg;
          showProtocolHeatmapView();
          renderProtocolHeatmap();
          updateWeeklyKPI();
      } catch(e) { alert('Netzwerkfehler.'); }
  }

  function updateWeeklyKPI() {
      const days = _protocolWeekDates(0);
      const today = _isoDate(new Date());
      const dayIsos = days.map(_isoDate).filter(iso => iso <= today);
      const cfg = completenessConfig.containers || {};
      let expected = 0, performed = 0, hasIssues = false;

      Object.entries(cfg).filter(([_,v]) => v.requires_check).forEach(([name, v]) => {
          const freq = v.frequency || '2x_daily';
          const ce = protocolLog.filter(e => e.container === name);
          if (freq === '1x_weekly') {
              expected++;
              if (dayIsos.some(iso => ce.some(e => e.date===iso))) performed++;
              if (ce.some(e => dayIsos.includes(e.date) && e.status==='unvollständig')) hasIssues = true;
          } else if (freq === '2x_daily') {
              dayIsos.forEach(iso => {
                  expected += 2;
                  const de = ce.filter(e => e.date===iso);
                  if (de.some(e => e.shift==='F')) performed++;
                  if (de.some(e => e.shift==='S')) performed++;
                  if (de.some(e => e.status==='unvollständig')) hasIssues = true;
              });
          } else {
              dayIsos.forEach(iso => {
                  expected++;
                  const de = ce.filter(e => e.date===iso);
                  if (de.length) performed++;
                  if (de.some(e => e.status==='unvollständig')) hasIssues = true;
              });
          }
      });

      const el = document.getElementById('weeklyCheckCount');
      if (!el) return;
      el.textContent = expected > 0 ? `${performed}/${expected}` : '–';
      el.className = 'text-2xl font-bold ' + (
          expected === 0 ? 'text-slate-800' :
          performed === expected && !hasIssues ? 'text-green-600' : 'text-orange-500'
      );
  }

  // KPI beim Laden befüllen
  (async () => {
      try {
          const res = await fetch('/api/completeness_log');
          if (res.ok) { protocolLog = await res.json(); updateWeeklyKPI(); }
      } catch(e) {}
  })();
</script>

</body></html>
"""

MAINTENANCE_PAGE_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Maintenance (SWAT)</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body { font-family: 'Inter', sans-serif; background-color: #f8fafc; }
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .text-primary { color: #3B82F6; }
        .bg-primary { background-color: #3B82F6; }
        .custom-scrollbar::-webkit-scrollbar { width: 8px; height: 8px; }
        .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
        .custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
        .custom-scrollbar:hover::-webkit-scrollbar-thumb { background: #94a3b8; }
        .modal { transition: opacity 0.2s ease; z-index: 50; }
        body.modal-active { overflow-y: hidden; }
        .modal-backdrop { position: fixed; inset: 0; background-color: rgba(0,0,0,0.5); z-index: 40; }
        .modal-content { position: relative; margin: 2rem auto; border-radius: 0.5rem; background-color: white; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); z-index: 50; display: flex; flex-direction: column; max-height: 90vh; }
        .attachment-item { @apply flex items-center gap-2 p-2 bg-slate-50 rounded border border-slate-200 text-sm; }
        .attachment-item img { @apply w-8 h-8 object-cover rounded; }

        .checklist-item { display: flex; align-items: center; gap: 12px; padding: 12px; margin-bottom: 8px; border-radius: 8px; border: 1px solid #e2e8f0; background-color: #ffffff; cursor: pointer;
            transition: all 0.2s;
            width: 100%;
            box-shadow: 0 1px 2px rgba(0,0,0,0.05);
        }

        .checklist-item:hover {
            border-color: #60a5fa; /* blue-400 */
            background-color: #f8fafc; /* slate-50 */
        }

        .checklist-item:last-child { margin-bottom: 0; }
        .checklist-item input[type="checkbox"] { 
            width: 20px;
            height: 20px;
            border-radius: 4px;
            border: 1px solid #cbd5e1;
            color: #2563eb; /* blue-600 */
            margin-right: 4px;
            flex-shrink: 0;
            cursor: pointer;
        }
        .checklist-item span { font-size: 0.875rem; font-weight: 500; color: #334155; line-height: 1.4; flex-grow: 1;}

        .checklist-item.checked { background-color: #eff6ff !important; border-color: #bfdbfe !important;}
        .checklist-item.checked span { color: #1e3a8a; font-weight: 600;}

        .nav-chip { @apply inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium border cursor-pointer transition-colors; }
        .nav-chip:hover { @apply opacity-80; }

        .drag-handle { cursor: grab; color: #94a3b8; }
        .drag-handle:active { cursor: grabbing; }
        .task-row { @apply flex items-center p-3 border-b border-slate-100 hover:bg-slate-50 transition-colors cursor-pointer; }
        .task-row:last-child { @apply border-b-0; }
        .task-meta-col { @apply flex-shrink-0 flex items-center gap-4 text-right; }
        .tag-chip { display: inline-flex; align-items: center; padding: 0.125rem 0.625rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; background-color: #dbeafe; color: #1e40af; border: 1px solid #bfdbfe; margin-right: 0.5rem; margin-bottom: 0.5rem; }
        .tag-chip-remove { margin-left: 0.375rem; color: #2563eb; cursor: pointer; font-weight: 700; font-size: 1rem; line-height: 1; }
        .tag-chip-remove:hover { color: #1e40af; }
        .analyse-toggle-btn { transition: all 0.2s; }
        .analyse-toggle-btn.active {
        background-color: #ffffff;
        color: #0369a1; /* sky-700 */
        font-weight: 600;
        box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
    }
    </style>
</head>
<body class="bg-white text-slate-800">
<div class="flex h-screen bg-white">
    {% include 'sidebar.html' %}
    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200 sticky top-0 z-30">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Maintenance Center</h1>
             <div class="flex items-center gap-4">
                <div class="text-sm text-slate-600">Angemeldet als: <strong>{{ current_user_display_name }}</strong></div>
                {% if session.get('is_admin') %}
                <span class="bg-purple-100 text-purple-800 text-xs font-medium px-2.5 py-0.5 rounded border border-purple-200">Admin</span>
                {% endif %}
             </div>
        </header>
        <div class="p-4 sm:p-6 lg:p-8">
            <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
                <div class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4 border border-slate-200">
                    <div class="p-3 bg-blue-50 rounded-full text-blue-600"><span class="material-icons-outlined text-3xl">assignment</span></div>
                    <div><p class="text-sm text-slate-500">Offene Aufträge</p><p class="text-2xl font-bold text-slate-800" id="openTasksCount">0</p></div>
                </div>
                <div class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4 border border-slate-200">
                    <div class="p-3 bg-yellow-50 rounded-full text-yellow-600"><span class="material-icons-outlined text-3xl">warning</span></div>
                    <div><p class="text-sm text-slate-500">Offene Issues</p><p class="text-2xl font-bold text-slate-800" id="openIssuesCount">0</p></div>
                </div>
                <div class="bg-white p-5 rounded-lg shadow-md flex items-center space-x-4 border border-slate-200">
                    <div class="p-3 bg-green-50 rounded-full text-green-600"><span class="material-icons-outlined text-3xl">done_all</span></div>
                    <div><p class="text-sm text-slate-500">Erledigt (Gesamt)</p><p class="text-2xl font-bold text-slate-800" id="completedTasksCount">0</p></div>
                </div>
            </div>

            <div class="mb-6">
                <!-- Jinja Logik: Wer darf die erweiterten Tabs sehen? -->
                {% set show_admin_tabs = 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles',[]) or 'MAINTENANCEMANAGER' in session.get('roles',[]) or 'TESTENGINEER' in session.get('roles',[]) %}
                
                <nav class="flex space-x-6 border-b border-slate-200 overflow-x-auto">
                    <button class="tab-btn px-1 pb-3 text-sm font-medium text-primary border-b-2 border-primary whitespace-nowrap" onclick="switchTab('active', this)">Aktive Aufträge</button>
                    
                    {% if show_admin_tabs %}
                    <button class="tab-btn px-1 pb-3 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent hover:border-slate-300 whitespace-nowrap" onclick="switchTab('definitions', this)">Wartungsplan</button>
                    {% endif %}
                    
                    <button class="tab-btn px-1 pb-3 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent hover:border-slate-300 whitespace-nowrap" onclick="switchTab('logbook', this)">Logbuch</button>
                    
                    {% if show_admin_tabs %}
                    <button class="tab-btn px-1 pb-3 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent hover:border-slate-300 whitespace-nowrap" onclick="switchTab('history', this)">Historie / Archiv</button>
                    <button class="tab-btn px-1 pb-3 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent hover:border-slate-300 whitespace-nowrap" onclick="switchTab('companies', this)">Externe Firmen</button>
                    <button class="tab-btn px-1 pb-3 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent hover:border-slate-300 whitespace-nowrap" onclick="switchTab('analysis', this)">Analyse</button>
                    {% endif %}
                </nav>
            </div>

            <div id="tab-active" class="tab-content">
                <div class="flex flex-col gap-4 mb-6">
                    <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
                        <h2 class="text-xl font-bold text-slate-800">Wartungsübersicht</h2>
                        
                        <div class="flex items-center gap-3 flex-wrap">
                            <!-- Suche -->
                            <div class="relative">
                                <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">search</span>
                                <input type="text" id="searchActiveOrders" oninput="applyFiltersActive()" placeholder="Aufträge suchen..." class="pl-9 pr-3 py-1.5 rounded-full border border-slate-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm w-40 transition-all focus:w-64">
                            </div>
                            <!-- Filter Toggle Button -->
                            <div class="relative flex-shrink-0">
                                <button onclick="toggleMaintenanceFilter('filterPanelActive', 'filterBadgeActive')" title="Filter" class="w-8 h-8 flex items-center justify-center rounded-full border border-slate-300 bg-white hover:bg-slate-50 text-slate-600 transition-colors shadow-sm">
                                    <span class="material-icons-outlined text-base">filter_list</span>
                                </button>
                                <span id="filterBadgeActive" class="absolute -top-1 -right-1 w-4 h-4 bg-blue-600 text-white text-[10px] font-bold rounded-full hidden items-center justify-center pointer-events-none">0</span>
                            </div>
                            <!-- Action Button -->
                            <button class="bg-yellow-500 text-white px-3 py-1.5 rounded-full text-xs font-medium hover:bg-yellow-600 flex items-center gap-1 shadow-sm" onclick="openAdHocContextModal()">
                                <span class="material-icons-outlined text-sm">report_problem</span> Issue melden
                            </button>
                        </div>
                    </div>
                    <!-- Filter Accordion Panel -->
                    <div id="filterPanelActive" class="hidden">
                        <div class="flex flex-wrap justify-end gap-2 pt-3 pb-1 border-t border-slate-100">
                            <!-- Typ -->
                            <div class="relative">
                                <select id="filterTypeActive" onchange="applyFiltersActive()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Typ: Alle</option>
                                    <option value="M">Wartung</option>
                                    <option value="I">Issue</option>
                                    <option value="A">Maßnahme</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Prio -->
                            <div class="relative">
                                <select id="filterPrioActive" onchange="applyFiltersActive()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Prio: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Blocker Filter -->
                            <div class="relative">
                                <select id="filterBlockerActive" onchange="applyFiltersActive()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Sperre: Alle</option>
                                    <option value="any">Ja (Alle Blocker)</option>
                                    <option value="unit">Nur Anlage (Unit)</option>
                                    <option value="hardware">Nur Hardware</option>
                                    <option value="none">Nein (Keine Sperre)</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Bereich -->
                            <div class="relative">
                                <select id="filterAreaActive" onchange="applyFiltersActive()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Bereich: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Anlage -->
                            <div class="relative">
                                <select id="filterPlantActive" onchange="applyFiltersActive()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Anlage: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Reset Button -->
                            <button id="filterResetActive" onclick="resetFiltersActive()" class="hidden items-center gap-1 pl-2 pr-3 py-1.5 rounded-full border border-red-200 bg-red-50 text-red-600 text-xs font-medium hover:bg-red-100 transition-colors">
                                <span class="material-icons-outlined text-sm">close</span> Zurücksetzen
                            </button>
                        </div>
                    </div>
                </div>
                <div id="groupCritical" class="mb-6 bg-white rounded-lg shadow border border-red-200 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-red-50 hover:bg-red-100 rounded-t-lg transition-colors text-left" onclick="toggleGroup('bodyCritical', this)">
                        <div class="flex items-center gap-2 text-red-800 font-bold">
                            <span class="material-icons-outlined">priority_high</span> Kritisch / Unbewertet
                            <span id="countCritical" class="ml-2 px-2 py-0.5 bg-red-200 text-red-900 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-red-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyCritical" class="p-0 hidden"></div>
                </div>

                <div id="groupOverdue" class="mb-6 bg-white rounded-lg shadow border border-amber-300 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-amber-50 hover:bg-amber-100 rounded-t-lg transition-colors text-left" onclick="toggleGroup('bodyOverdue', this)">
                        <div class="flex items-center gap-2 text-amber-800 font-bold">
                            <span class="material-icons-outlined">warning</span> Überfällig
                            <span id="countOverdue" class="ml-2 px-2 py-0.5 bg-amber-200 text-amber-900 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-amber-500 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyOverdue" class="p-0 hidden"></div>
                </div>
                
                <div id="groupUpcoming" class="mb-6 bg-white rounded-lg shadow border border-slate-200 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-slate-50 hover:bg-slate-100 rounded-t-lg transition-colors text-left" onclick="toggleGroup('bodyUpcoming', this)">
                        <div class="flex items-center gap-2 text-slate-700 font-bold">
                            <span class="material-icons-outlined text-blue-600">event</span> Anstehend (14 Tage) 
                            <span id="countUpcoming" class="ml-2 px-2 py-0.5 bg-slate-200 text-slate-700 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyUpcoming" class="p-0 hidden"></div>
                </div>
                
                <div id="groupOutlook" class="mb-6 bg-white rounded-lg shadow border border-slate-200 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-white hover:bg-slate-50 rounded-t-lg transition-colors text-left border-b border-slate-100" onclick="toggleGroup('bodyOutlook', this)">
                        <div class="flex items-center gap-2 text-slate-600 font-semibold">
                            <span class="material-icons-outlined">date_range</span> Vorschau (Woche 3-8) 
                            <span id="countOutlook" class="ml-2 px-2 py-0.5 bg-slate-100 text-slate-600 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyOutlook" class="p-0 hidden"></div>
                </div>
                
                <div id="groupFuture" class="mb-6 bg-white rounded-lg shadow border border-slate-200 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-white hover:bg-slate-50 rounded-t-lg transition-colors text-left border-b border-slate-100" onclick="toggleGroup('bodyFuture', this)">
                        <div class="flex items-center gap-2 text-slate-500 font-medium">
                            <span class="material-icons-outlined">update</span> Zukunft (> 8 Wochen) 
                            <span id="countFuture" class="ml-2 px-2 py-0.5 bg-slate-100 text-slate-600 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyFuture" class="p-0 hidden"></div>
                </div>
                
                <div id="groupIssues" class="mb-6 bg-white rounded-lg shadow border border-orange-200 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-orange-50 hover:bg-orange-100 rounded-t-lg transition-colors text-left border-b border-orange-100" onclick="toggleGroup('bodyIssues', this)">
                        <div class="flex items-center gap-2 text-orange-800 font-bold">
                            <span class="material-icons-outlined">report_problem</span> Offene Mängel / Störungen 
                            <span id="countIssues" class="ml-2 px-2 py-0.5 bg-orange-200 text-orange-900 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-orange-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyIssues" class="p-0 hidden"></div>
                </div>
                <div id="emptyStateActive" class="hidden p-12 text-center bg-white rounded-lg border border-dashed border-slate-300"><span class="material-icons-outlined text-4xl text-slate-300 mb-3">task_alt</span><p class="text-slate-500">Keine offenen Aufträge gefunden.</p></div>
            </div>

            <div id="tab-history" class="tab-content hidden">
                <div class="bg-white rounded-lg shadow p-6 border border-slate-200">
                    <h2 class="text-lg font-bold text-slate-800 mb-4">Abgeschlossene Aufträge</h2>
                    <div class="overflow-x-auto custom-scrollbar">
                        <table class="w-full text-sm text-left"><thead class="text-xs text-gray-700 uppercase bg-gray-50"><tr><th class="px-6 py-3">ID</th><th class="px-6 py-3">Aufgabe</th><th class="px-6 py-3">Erledigt am</th><th class="px-6 py-3">Durch</th><th class="px-6 py-3 text-right">Protokoll</th></tr></thead><tbody id="historyOrdersBody" class="divide-y divide-slate-100"></tbody></table>
                    </div>
                </div>
            </div>

            <!-- TAB DEFINITIONS (Wartungsplan) -->
            <div id="tab-definitions" class="tab-content hidden">
                <div class="flex flex-col gap-4 mb-6">
                    <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
                        <h2 class="text-xl font-bold text-slate-800">Wartungsplan Definitionen</h2>
                        
                        <div class="flex items-center gap-3 flex-wrap">                            
                            <!-- Suche -->
                            <div class="relative">
                                <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">search</span>
                                <input type="text" id="searchDefinitions" oninput="applyFiltersDefinitions()" placeholder="Pläne suchen..." class="pl-9 pr-3 py-1.5 rounded-full border border-slate-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm w-40 transition-all focus:w-64">
                            </div>
                            <!-- Filter Toggle Button -->
                            <div class="relative flex-shrink-0">
                                <button onclick="toggleMaintenanceFilter('filterPanelDef', 'filterBadgeDef')" title="Filter" class="w-8 h-8 flex items-center justify-center rounded-full border border-slate-300 bg-white hover:bg-slate-50 text-slate-600 transition-colors shadow-sm">
                                    <span class="material-icons-outlined text-base">filter_list</span>
                                </button>
                                <span id="filterBadgeDef" class="absolute -top-1 -right-1 w-4 h-4 bg-blue-600 text-white text-[10px] font-bold rounded-full hidden items-center justify-center pointer-events-none">0</span>
                            </div>
                            <!-- Action Button -->
                            <button class="bg-primary text-white px-3 py-1.5 rounded-full text-xs font-medium hover:bg-blue-600 flex items-center gap-1 shadow-sm" onclick="openDefinitionModal()">
                                <span class="material-icons-outlined text-sm">add</span> Neue Vorlage
                            </button>
                        </div>
                    </div>
                    <!-- Filter Accordion Panel -->
                    <div id="filterPanelDef" class="hidden">
                        <div class="flex flex-wrap justify-end gap-2 pt-3 pb-1 border-t border-slate-100">
                            <!-- Intervall -->
                            <div class="relative">
                                <select id="filterIntervalDef" onchange="applyFiltersDefinitions()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Intervall: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Bereich -->
                            <div class="relative">
                                <select id="filterAreaDef" onchange="applyFiltersDefinitions()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Bereich: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Anlage -->
                            <div class="relative">
                                <select id="filterPlantDef" onchange="applyFiltersDefinitions()" class="filter-chip appearance-none bg-none pl-3 pr-8 py-1.5 rounded-full border border-slate-300 text-xs font-medium text-slate-600 bg-white hover:bg-slate-50 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors">
                                    <option value="all">Anlage: Alle</option>
                                </select>
                                <span class="material-icons-outlined absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
                            </div>
                            <!-- Reset Button -->
                            <button id="filterResetDef" onclick="resetFiltersDef()" class="hidden items-center gap-1 pl-2 pr-3 py-1.5 rounded-full border border-red-200 bg-red-50 text-red-600 text-xs font-medium hover:bg-red-100 transition-colors">
                                <span class="material-icons-outlined text-sm">close</span> Zurücksetzen
                            </button>
                        </div>
                    </div>
                </div>
            
                <!-- Gruppe 1: Wöchentlich -->
                <div id="groupDefWeekly" class="mb-6 bg-white rounded-lg shadow border border-slate-200">
                    <button class="w-full flex justify-between items-center p-4 bg-slate-50 hover:bg-slate-100 rounded-t-lg transition-colors text-left border-b border-slate-200" onclick="toggleGroup('bodyDefWeekly', this)">
                        <div class="flex items-center gap-2 text-slate-700 font-bold">
                            <span class="material-icons-outlined text-slate-400">date_range</span> Wöchentliche Wartungen 
                            <span id="countDefWeekly" class="ml-2 px-2 py-0.5 bg-slate-200 text-slate-700 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyDefWeekly" class="p-0 overflow-x-auto custom-scrollbar hidden"></div>
                </div>
                
                <!-- Gruppe 2: Monatlich -->
                <div id="groupDefMonthly" class="mb-6 bg-white rounded-lg shadow border border-slate-200">
                    <button class="w-full flex justify-between items-center p-4 bg-slate-50 hover:bg-slate-100 rounded-t-lg transition-colors text-left border-b border-slate-200" onclick="toggleGroup('bodyDefMonthly', this)">
                        <div class="flex items-center gap-2 text-slate-700 font-bold">
                            <span class="material-icons-outlined text-slate-400">calendar_month</span> Monatliche Wartungen 
                            <span id="countDefMonthly" class="ml-2 px-2 py-0.5 bg-slate-200 text-slate-700 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyDefMonthly" class="p-0 overflow-x-auto custom-scrollbar hidden"></div>
                </div>
                
                <!-- Gruppe 3: Jährlich -->
                <div id="groupDefYearly" class="mb-6 bg-white rounded-lg shadow border border-slate-200">
                    <button class="w-full flex justify-between items-center p-4 bg-slate-50 hover:bg-slate-100 rounded-t-lg transition-colors text-left border-b border-slate-200" onclick="toggleGroup('bodyDefYearly', this)">
                        <div class="flex items-center gap-2 text-slate-700 font-bold">
                            <span class="material-icons-outlined text-slate-400">event_repeat</span> Jährliche Wartungen 
                            <span id="countDefYearly" class="ml-2 px-2 py-0.5 bg-slate-200 text-slate-700 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyDefYearly" class="p-0 overflow-x-auto custom-scrollbar hidden"></div>
                </div>
                
                <!-- Gruppe 4: Sonstige -->
                <div id="groupDefOther" class="mb-6 bg-white rounded-lg shadow border border-slate-200 hidden">
                    <button class="w-full flex justify-between items-center p-4 bg-slate-50 hover:bg-slate-100 rounded-t-lg transition-colors text-left border-b border-slate-200" onclick="toggleGroup('bodyDefOther', this)">
                        <div class="flex items-center gap-2 text-slate-700 font-bold">
                            <span class="material-icons-outlined text-slate-400">help_outline</span> Sonstige Intervalle 
                            <span id="countDefOther" class="ml-2 px-2 py-0.5 bg-slate-200 text-slate-700 text-xs rounded-full">0</span>
                        </div>
                        <span class="material-icons-outlined text-slate-400 transform rotate-180 transition-transform duration-200">expand_less</span>
                    </button>
                    <div id="bodyDefOther" class="p-0 overflow-x-auto custom-scrollbar hidden"></div>
                </div>
            </div>

            <div id="tab-companies" class="tab-content hidden">
                <div class="bg-white rounded-lg shadow p-6 border border-slate-200">
                    <div class="flex justify-between items-center mb-4">
                        <h2 class="text-lg font-bold text-slate-800">Externe Dienstleister</h2>
                        <button class="bg-primary text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-600 flex items-center gap-2 shadow-sm" onclick="openCompanyModal()">
                            <span class="material-icons-outlined text-base">add</span> Neue Firma
                        </button>
                    </div>
                    <div class="overflow-x-auto custom-scrollbar">
                        <table class="w-full text-sm text-left">
                            <thead class="text-xs text-gray-700 uppercase bg-gray-50">
                                <tr>
                                    <th class="px-6 py-3 cursor-pointer select-none hover:bg-gray-100 transition-colors" onclick="sortCompanies('name')">
                                        <div class="flex items-center gap-1">Name<span id="sort-icon-name" class="material-icons-outlined text-slate-400" style="font-size:14px">unfold_more</span></div>
                                    </th>
                                    <th class="px-6 py-3 cursor-pointer select-none hover:bg-gray-100 transition-colors" onclick="sortCompanies('contact')">
                                        <div class="flex items-center gap-1">Ansprechpartner<span id="sort-icon-contact" class="material-icons-outlined text-slate-400" style="font-size:14px">unfold_more</span></div>
                                    </th>
                                    <th class="px-6 py-3 cursor-pointer select-none hover:bg-gray-100 transition-colors" onclick="sortCompanies('services')">
                                        <div class="flex items-center gap-1">Leistungen<span id="sort-icon-services" class="material-icons-outlined text-slate-400" style="font-size:14px">unfold_more</span></div>
                                    </th>
                                    <th class="px-6 py-3 text-right">Aktionen</th>
                                </tr>
                            </thead>
                            <tbody id="companiesBody" class="divide-y divide-slate-100"></tbody>
                        </table>
                    </div>
                </div>
            </div>
            <div id="tab-analysis" class="tab-content hidden bg-white rounded-lg p-6"> <!-- bg-white und padding hier hinzugefügt für den Wrapper-Look -->
                
                <!-- Header -->
                <div class="mb-6">
                    <h2 class="text-2xl font-bold text-slate-900 mb-2">Maintenance Intelligence</h2> <!-- mb-2 für Abstand -->
                    <p class="text-sm text-slate-600">Datenbasierte Einblicke in Wartung und Betrieb.</p>
                </div>
            
                <!-- Sub-Navigation (Linksbündig, wie im Screenshot) -->
                <div class="mb-6">
                    <div class="inline-flex rounded-md shadow-sm bg-slate-100 p-1">
                        <button onclick="switchAnalysisView('operational', this)" class="analyse-toggle-btn active rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Operativ</button>
                        <button onclick="switchAnalysisView('performance', this)" class="analyse-toggle-btn rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Performance</button>
                        <button onclick="switchAnalysisView('history', this)" class="analyse-toggle-btn rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Historie</button> <!-- NEU -->
                        <button onclick="switchAnalysisView('partner', this)" class="analyse-toggle-btn rounded-md px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-white">Partner</button>
                    </div>
                </div>
            
                <!-- VIEW 1: OPERATIV -->
                <div id="view-operational" class="analysis-subview">
                    <!-- Reihe 1: KPI Pulse (Reduziert auf 2) -->
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
                        <div class="bg-white p-5 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4">
                            <div class="w-12 h-12 rounded-full bg-blue-50 flex items-center justify-center text-blue-500"><span class="material-icons-outlined">timer</span></div>
                            <div><p class="text-xs font-bold text-slate-400 uppercase tracking-wider">Ø Lösungszeit (Issues)</p><p class="text-2xl font-bold text-slate-800" id="kpi-avg-time">0 Tage</p></div>
                        </div>
                        <div class="bg-white p-5 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4">
                            <div class="w-12 h-12 rounded-full bg-green-50 flex items-center justify-center text-green-500"><span class="material-icons-outlined">check_circle</span></div>
                            <div><p class="text-xs font-bold text-slate-400 uppercase tracking-wider">Wartungs-Compliance (On-Time)</p><p class="text-2xl font-bold text-slate-800" id="kpi-ontime">0%</p></div>
                        </div>
                    </div>
                
                    <!-- Reihe 2: Tabelle und Product Type Donut -->
                    <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
                        <!-- Asset Hotspots Tabelle (2/3) -->
                        <div class="lg:col-span-2 bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex flex-col">
                            <!-- ... Header (Suche) bleibt gleich, kopiere ihn rein oder lass ihn ... -->
                            <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
                                <h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
                                    <span class="material-icons-outlined text-orange-500">inventory_2</span> 
                                    Hardware Störungen
                                </h3>
                                <div class="relative">
                                    <span class="material-icons-outlined absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-sm">search</span>
                                    <input type="text" id="asset-search-input" oninput="renderAssetTable()" placeholder="Asset suchen..." class="pl-8 pr-3 py-1.5 text-sm border border-slate-200 rounded-md focus:ring-blue-500 focus:border-blue-500 w-48 transition-all focus:w-64">
                                </div>
                            </div>
                            <div class="overflow-x-auto flex-grow custom-scrollbar">
                                <table class="w-full text-sm text-left">
                                    <thead class="text-xs text-slate-500 uppercase bg-slate-50 border-b border-slate-200">
                                        <tr>
                                            <th class="px-4 py-3 font-bold">Asset / Komponente</th>
                                            <th class="px-4 py-3 font-bold text-center">Issues</th>
                                            <th class="px-4 py-3 font-bold text-center">Kritisch</th>
                                            <th class="px-4 py-3 font-bold text-right">Letzte Meldung</th>
                                        </tr>
                                    </thead>
                                    <tbody id="asset-hotspot-table-body" class="divide-y divide-slate-100"></tbody>
                                </table>
                                <div id="asset-table-footer" class="mt-3 text-xs text-center text-slate-400 italic hidden"></div>
                            </div>
                        </div>
                    
                        <!-- NEU: Product Type Chart (1/3) -->
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
                            <h3 class="text-lg font-bold text-slate-800 mb-4">Issues nach Product Type</h3>
                            <div class="relative h-64 w-full flex justify-center">
                                <canvas id="chart-product-types"></canvas> <!-- Neue ID -->
                            </div>
                            <p class="text-xs text-slate-400 text-center mt-4">Basiert auf den verknüpften Assets der Issues.</p>
                        </div>
                    </div>
                    
                    <!-- Reihe 3: Bereichs-Performance (Tabelle & Donut) -->
                    <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
                        <!-- Tabelle (2/3) -->
                        <div class="lg:col-span-2 bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex flex-col">
                            <h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2">
                                <span class="material-icons-outlined text-blue-500">category</span> 
                                Performance nach Bereich
                            </h3>
                            <div class="overflow-x-auto flex-grow custom-scrollbar">
                                <table class="w-full text-sm text-left">
                                    <thead class="text-xs text-slate-500 uppercase bg-slate-50 border-b border-slate-200">
                                        <tr>
                                            <th class="px-4 py-3 font-bold">Bereich</th>
                                            <th class="px-4 py-3 font-bold text-center">Issues</th>
                                            <th class="px-4 py-3 font-bold text-center">Kritisch</th>
                                            <th class="px-4 py-3 font-bold text-center">Ø Zeit</th>
                                            <th class="px-4 py-3 font-bold w-1/4">On-Time</th>
                                        </tr>
                                    </thead>
                                    <tbody id="area-performance-table-body" class="divide-y divide-slate-100 text-slate-700"></tbody>
                                </table>
                            </div>
                        </div>
                    
                        <!-- Donut (1/3) -->
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
                            <h3 class="text-lg font-bold text-slate-800 mb-4">Issues nach Bereich</h3>
                            <div class="relative h-64 w-full flex justify-center">
                                <canvas id="chart-areas"></canvas> <!-- Alte ID wiederverwendet -->
                            </div>
                            <p class="text-xs text-slate-400 text-center mt-4">Anteil der gemeldeten Issues.</p>
                        </div>
                    </div>
                </div>
                
                <!-- VIEW 2: PERFORMANCE -->
                <div id="view-performance" class="analysis-subview hidden">
                    <!-- HIER IST DIE ÄNDERUNG: lg:grid-cols-2 sorgt für das Nebeneinander -->
                    <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
                        
                        <!-- Trend (Links) -->
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
                            <h3 class="text-lg font-bold text-slate-800 mb-4">Pünktlichkeits-Trend (12 Monate)</h3>
                            <!-- Höhe auf 450px fixiert für Symmetrie -->
                            <div class="relative h-[450px] w-full">
                                <canvas id="chart-trend"></canvas>
                            </div>
                        </div>
                
                        <!-- Intervall (Rechts) -->
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
                            <h3 class="text-lg font-bold text-slate-800 mb-4">Intervall-Treue (Soll vs. Ist)</h3>
                            <p class="text-xs text-slate-500 mb-4">Punkte oberhalb der Linie zeigen verspätete Wartungen.</p>
                            <!-- Höhe auf 450px fixiert für Symmetrie -->
                            <div class="relative h-[450px] w-full">
                                <canvas id="chart-intervals"></canvas>
                            </div>
                        </div>
                
                    </div>
                </div>
         
                <!-- VIEW: HISTORIE (Nach Betriebsbereich) -->
                <div id="view-history" class="analysis-subview hidden">
                    <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
                        
                        <!-- Tabelle (2/3) -->
                        <div class="lg:col-span-2 bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex flex-col">
                            <h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2">
                                <span class="material-icons-outlined text-purple-500">history</span> 
                                Historie nach Betriebsbereich
                            </h3>
                            <div class="overflow-x-auto flex-grow custom-scrollbar">
                                <table class="w-full text-sm text-left">
                                    <thead class="text-xs text-slate-500 uppercase bg-slate-50 border-b border-slate-200">
                                        <tr>
                                            <th class="px-4 py-3 font-bold">Betriebsbereich (Unit)</th>
                                            <th class="px-4 py-3 font-bold text-center">Aufträge (Gesamt)</th>
                                            <th class="px-4 py-3 font-bold text-center">Davon Issues</th>
                                            <th class="px-4 py-3 font-bold text-right">Letzter Abschluss</th>
                                        </tr>
                                    </thead>
                                    <tbody id="history-unit-table-body" class="divide-y divide-slate-100"></tbody>
                                </table>
                            </div>
                        </div>
                
                        <!-- Donut (1/3) -->
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
                            <h3 class="text-lg font-bold text-slate-800 mb-4">Arbeitsverteilung (Bereiche)</h3>
                            <div class="relative h-64 w-full flex justify-center">
                                <canvas id="chart-history-units"></canvas>
                            </div>
                            <p class="text-xs text-slate-400 text-center mt-4">Anteil abgeschlossener Aufträge.</p>
                        </div>
                    </div>
                </div>            
                <!-- VIEW 3: PARTNER -->
                <div id="view-partner" class="analysis-subview hidden">
                    <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
                        <h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"><span class="material-icons-outlined text-indigo-500">business</span> Externe Partner Performance</h3>
                        <div class="overflow-x-auto">
                            <table class="w-full text-sm text-left">
                                <thead class="text-xs text-slate-500 uppercase bg-slate-50 border-b border-slate-200">
                                    <tr>
                                        <th class="px-6 py-3 font-bold">Firma</th>
                                        <th class="px-6 py-3 font-bold text-center">Aufträge (12M)</th>
                                        <th class="px-6 py-3 font-bold text-center">Ø Dauer</th>
                                        <th class="px-6 py-3 font-bold w-1/3">Pünktlichkeit</th>
                                        <th class="px-6 py-3 font-bold text-center">Aktuell Offen</th>
                                    </tr>
                                </thead>
                                <tbody id="partner-table-body" class="divide-y divide-slate-100 text-slate-700"></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
                   <!-- TAB LOGBOOK -->
                <div id="tab-logbook" class="tab-content hidden">
                    <div class="flex justify-between items-center mb-6">
                        <h2 class="text-xl font-bold text-slate-800">Betriebstagebuch</h2>
                        <div class="flex gap-3">
                             <div class="relative">
                                <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">search</span>
                                <input type="text" id="logbook-search" oninput="renderLogbook()" placeholder="Einträge durchsuchen..." class="pl-9 pr-3 py-1.5 rounded-full border border-slate-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm w-40 transition-all focus:w-64">
                            </div>
                            <button class="bg-indigo-600 text-white px-3 py-1.5 rounded-full text-xs font-medium hover:bg-indigo-700 flex items-center gap-1 shadow-sm" onclick="openCreateLogModal()">
                                <span class="material-icons-outlined text-sm">edit_note</span> Neuer Eintrag
                            </button>
                        </div>
                    </div>
                    
                    <div id="logbook-container" class="space-y-6">
                        <!-- Wird per JS gefüllt (Monats-Gruppen) -->
                    </div>
                </div>
                
                <!-- LOG ENTRY MODAL (Create & Edit) -->
                <div id="createLogModal" class="modal hidden fixed inset-0 z-[60] flex items-center justify-center bg-gray-900 bg-opacity-50">
                    <div class="modal-backdrop" onclick="closeCreateLogModal()"></div>
                    <div class="modal-content max-w-lg">
                        <div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-lg">
                            <h3 class="text-xl font-bold text-slate-800" id="logModalTitle">Logbucheintrag</h3>
                            <div class="flex gap-2">
                                <button id="btnDeleteLog" onclick="deleteLogEntry()" class="text-slate-400 hover:text-red-600 p-1.5 rounded-full hover:bg-red-50 transition-colors hidden" title="Löschen">
                                    <span class="material-icons-outlined">delete</span>
                                </button>
                                <button onclick="closeCreateLogModal()" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-full hover:bg-slate-100 transition-colors">
                                    <span class="material-icons-outlined">close</span>
                                </button>
                            </div>
                        </div>
                        <div class="p-6 bg-slate-50 overflow-y-auto max-h-[70vh]">
                            <form id="createLogForm" class="space-y-4">
                                <input type="hidden" id="logId"> <!-- Hidden ID für Edit -->
                                
                                <div>
                                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Titel / Ereignis*</label>
                                    <input class="w-full border-slate-300 rounded-md text-sm" id="logTitle" required>
                                </div>
                                <div class="grid grid-cols-2 gap-4">
                                    <div>
                                        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Datum</label>
                                        <input type="date" class="w-full border-slate-300 rounded-md text-sm" id="logDate">
                                    </div>
                                    <div>
                                        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Bereich</label>
                                        <select class="w-full border-slate-300 rounded-md text-sm bg-white" id="logCategory"></select>
                                    </div>
                                        <div class="col-span-2">
                                        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Betriebsbereich (Anlage)</label>
                                        <input list="facilityUnitList" class="w-full border-slate-300 rounded-md text-sm" id="logPlantIndex" placeholder="z.B. 1.1 Pumpenraum">
                                    </div>
                                </div>
                                <div>
                                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Beschreibung</label>
                                    <textarea class="w-full border-slate-300 rounded-md text-sm" id="logDesc" rows="6"></textarea>
                                </div>
                                
                                <!-- Dateianhänge -->
                                <div>
                                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Anhänge</label>
                                    <div id="logDropZone" class="flex flex-col items-center justify-center w-full px-4 py-5 border-2 border-dashed border-slate-300 rounded-lg cursor-pointer hover:border-indigo-400 hover:bg-indigo-50 transition-colors text-center" onclick="document.getElementById('logFileInput').click()" ondragover="event.preventDefault(); this.classList.add('border-indigo-500','bg-indigo-50')" ondragleave="this.classList.remove('border-indigo-500','bg-indigo-50')" ondrop="handleLogDrop(event)">
                                        <span class="material-icons-outlined text-slate-400 text-3xl mb-1">attach_file</span>
                                        <span class="text-xs text-slate-400">Dateien hier ablegen oder <span class="text-indigo-600 font-medium">durchsuchen</span></span>
                                        <input type="file" id="logFileInput" multiple class="hidden" onchange="handleLogFileSelect(this)">
                                    </div>
                                    <div id="logAttachmentChips" class="mt-2 flex flex-wrap gap-2"></div>
                                </div>

                                <!-- Info-Bereich (nur bei Edit sichtbar) -->
                                <div id="logMetaInfo" class="hidden pt-4 mt-2 border-t border-slate-200 text-xs text-slate-400">
                                    Erstellt von <span id="logCreatedBy"></span> am <span id="logCreatedAt"></span>
                                </div>
                            </form>
                        </div>
                        <div class="p-4 border-t border-slate-200 bg-white flex justify-end gap-3 rounded-b-lg">
                            <button class="px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded" onclick="closeCreateLogModal()">Abbrechen</button>
                            <button id="btnSaveLog" class="px-4 py-2 text-sm bg-indigo-600 text-white rounded hover:bg-indigo-700 font-medium shadow-sm flex items-center gap-2" onclick="submitLogEntry()">
                                <span class="material-icons-outlined text-sm">save</span> Speichern
                            </button>
                        </div>
                    </div>
                </div>
    </main>
</div>

<!-- COMPANY MODAL -->
<div id="companyModal" class="modal hidden fixed inset-0 z-50 flex items-center justify-center bg-gray-900 bg-opacity-50">
    <div class="modal-backdrop" onclick="closeCompanyModal()"></div>
    <div class="modal-content max-w-lg w-full">
        <div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-lg">
            <h3 class="text-lg font-bold text-slate-800" id="compModalTitle">Firma anlegen</h3>
            <button onclick="closeCompanyModal()" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6 bg-slate-50">
            <form id="companyForm" class="space-y-4">
                <input type="hidden" id="compId">
                <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Firmenname*</label><input class="w-full border-slate-300 rounded-md text-sm" id="compName" required placeholder="z.B. Müller Service GmbH"></div>
                <div>
                    <div class="flex justify-between items-center mb-2">
                        <label class="block text-xs font-bold text-slate-500 uppercase">Ansprechpartner</label>
                        <button type="button" onclick="addContactRow()" class="flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 font-medium"><span class="material-icons-outlined text-sm">person_add</span> Kontakt hinzufügen</button>
                    </div>
                    <div id="compContactsList" class="space-y-2"></div>
                </div>
                <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Leistungen / Notizen</label><textarea class="w-full border-slate-300 rounded-md text-sm" id="compServices" rows="3" placeholder="z.B. Kalibrierung, Elektroarbeiten..."></textarea></div>
            </form>
        </div>
        <div class="p-4 border-t border-slate-200 bg-white flex justify-end gap-3 rounded-b-lg"><button class="px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded" onclick="closeCompanyModal()">Abbrechen</button><button class="px-4 py-2 text-sm bg-primary text-white rounded hover:bg-blue-700 font-medium shadow-sm" onclick="saveCompany()">Speichern</button></div>
    </div>
</div>
<!-- AD-HOC CONTEXT MODAL -->
<div id="adHocContextModal" class="modal hidden fixed inset-0 z-[80] flex items-center justify-center bg-gray-900 bg-opacity-70 backdrop-blur-sm">
    <div class="modal-backdrop" onclick="closeAdHocContextModal()"></div>
    <div class="modal-content max-w-2xl w-full">
        <div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-lg">
            <h3 class="text-xl font-bold text-slate-800 flex items-center gap-2">
                <span class="material-icons-outlined text-blue-600">link</span> Worauf bezieht sich das Problem?
            </h3>
            <button onclick="closeAdHocContextModal()" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6 overflow-y-auto bg-slate-50">
            <div class="relative mb-4">
                <span class="material-icons-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400">search</span>
                <input type="text" id="contextSearchInput" placeholder="Hardware-Asset nach S/N, P/N oder Name suchen..." class="w-full pl-10 pr-4 py-3 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 shadow-sm">
            </div>
            <div id="contextSearchResults" class="bg-white border border-slate-200 rounded-lg max-h-64 overflow-y-auto custom-scrollbar">
                <!-- Suchergebnisse werden hier eingefügt -->
                <p class="p-8 text-center text-slate-400 text-sm">Bitte geben Sie einen Suchbegriff ein.</p>
            </div>
            <div class="mt-6 text-center">
                 <p class="text-xs text-slate-400 mb-2">Oder, falls nicht zuordenbar:</p>
                 <button onclick="createUnlinkedIssue()" class="text-sm text-slate-600 hover:text-primary font-medium px-4 py-2 bg-white border border-slate-300 rounded-lg shadow-sm hover:border-primary">
                    Allgemeines Problem ohne Asset-Bezug melden
                </button>
            </div>
        </div>
    </div>
</div>
<!-- WORK ORDER MODAL (REDESIGNED with EDIT MODE) -->
<div id="workOrderModal" class="modal hidden fixed inset-0 z-50 bg-gray-900 bg-opacity-60 flex items-center justify-center backdrop-blur-sm">
    <div class="modal-content bg-white w-full max-w-6xl h-[90vh] rounded-xl shadow-2xl flex flex-col overflow-hidden">

        <!-- HEADER (FIXED) -->
        <div class="border-b border-slate-200 bg-white sticky top-0 z-20 flex-shrink-0">
            <!-- ZEILE 1: Navigation / Breadcrumbs -->
            <div class="flex justify-between items-center px-6 py-2 bg-slate-50 text-xs border-b border-slate-100">
                <div id="navParentContainer" class="flex items-center text-slate-500 truncate mr-4"></div>
                <div id="navChildrenContainer" class="flex items-center gap-2 overflow-x-auto no-scrollbar"></div>
            </div>

            <!-- ZEILE 2: Haupt-Titel & Meta -->
            <div class="px-6 py-4">
                <div class="flex justify-between items-start mb-2">
                    <div class="flex items-center gap-3">
                        <!-- ANZEIGE-MODUS TITEL -->
                        <h2 id="woTitle" class="text-xl font-bold text-slate-800 leading-tight"></h2>
                        <!-- BEARBEITUNGS-MODUS TITEL -->
                        <div id="woEditTitleContainer" class="hidden">
                            <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Titel (nur zur Info)</label>
                            <input id="woEditTitle" type="text" readonly class="w-full bg-slate-100 border-slate-300 rounded-md text-sm font-bold text-slate-600">
                        </div>
                        <span id="woStatusBadge" class="px-2 py-0.5 rounded text-xs font-bold uppercase tracking-wide"></span>
                        <div id="woReviewIcon" class="hidden text-purple-600" title="4-Augen-Prinzip (Review) erforderlich"><span class="material-icons-outlined text-lg">visibility</span></div>
                        <span id="woPrioBadge"></span>
                        <div id="woBlockerBadges" class="flex gap-2"></div>
                    </div>
                    <div class="flex items-center gap-2">
                        <button id="btnEditOrder" onclick="switchToWoEditMode()" class="text-slate-400 hover:text-blue-600 p-1.5 rounded-full hover:bg-blue-50 transition-colors" title="Bearbeiten"><span class="material-icons-outlined">edit</span></button>
                        <button id="btnDeleteOrder" onclick="deleteCurrentOrder()" class="text-slate-400 hover:text-red-600 p-1.5 rounded-full hover:bg-red-50 transition-colors" title="Löschen"><span class="material-icons-outlined">delete_outline</span></button>
                        <button onclick="closeWorkOrderModal()" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-full hover:bg-slate-100 transition-colors"><span class="material-icons-outlined">close</span></button>
                    </div>
                </div>
                <div class="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-slate-600">
                     <div class="flex items-center gap-1.5 text-slate-500" title="ID"><span class="font-mono text-xs border border-slate-200 px-1 rounded bg-slate-50" id="woId"></span></div>
                     <div class="flex items-center gap-1.5" title="Fälligkeit">
                        <span class="material-icons-outlined text-slate-400 text-base">event</span>
                        <!-- ANZEIGE-MODUS DATUM -->
                        <span id="woDateDisplay"></span>

                        <!-- NEU: Separater Span für Abschlussdatum -->
                        <span id="woCompletionDisplay" class="hidden text-green-700 font-medium ml-1 flex items-center gap-1"></span>
                    
                        <!-- BEARBEITUNGS-MODUS DATUM (Start) -->
                        <div id="woEditDateContainer" class="hidden flex items-center gap-1">
                            <input type="date" id="woEditDate" class="border-slate-300 rounded-md text-sm p-1">
                            <select id="woEditStartShift" class="border-slate-300 rounded-md text-sm p-1 bg-white">
                                <option value="AM">AM</option>
                                <option value="PM">PM</option>
                            </select>
                        </div>
                    </div>
                     <div class="flex items-center gap-1.5 min-w-0">
                        <span class="material-icons-outlined text-slate-400 text-base flex-shrink-0">place</span>
                        
                        <!-- View Mode -->
                        <div id="woLocationView" class="flex items-center gap-1 truncate">
                            <span id="woTestbench"></span> 
                            <span id="woIndex" class="font-bold text-slate-700"></span>
                            
                            <!-- HIER WIEDER EINFÜGEN: -->
                            <span class="text-slate-300 mx-1 hidden" id="woTopicSeparator">|</span>
                            <span id="woTopic" class="text-blue-600 font-medium"></span>
                        </div>

                        <!-- Edit Mode (NEU) -->
                        <div id="woLocationEdit" class="hidden flex-grow max-w-md relative">
                            <input list="facilityUnitList" id="woEditPlantIndex" 
                                   onchange="onFacilityUnitSelected(this, 'woEditFacilityUnitId')"
                                   class="w-full border-slate-300 rounded-md text-xs py-1 px-2 focus:ring-2 focus:ring-blue-100 focus:border-blue-400 bg-white shadow-sm transition-all" 
                                   placeholder="Betriebsbereich (z.B. 1.1)">
                            <input type="hidden" id="woEditFacilityUnitId">
                        </div>
                     </div>
                </div>
            </div>
        </div>

        <!-- BODY (SPLIT VIEW) -->
        <div class="flex-grow flex overflow-hidden">
            <div class="w-7/12 border-r border-slate-200 overflow-y-auto custom-scrollbar p-6 bg-white space-y-8">
                <!-- ERSETZE diesen Block in der linken Spalte des workOrderModal -->
                <div>
                    <h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Beschreibung / Auftrag</h3>
                    <div class="p-4 bg-slate-50 border border-slate-200 rounded-lg shadow-sm">
                        <div id="woAssetTags" class="flex flex-wrap empty:hidden"></div>
                        
                        <!-- ANZEIGE-MODUS (VIEW) -->
                        <div id="woDescriptionViewContainer" class="text-sm text-slate-700 leading-relaxed gap-1.5 mt-3 pt-3 border-t border-slate-200/60 whitespace-pre-wrap">
                            <!-- Beschreibungstext wird hier von JS eingefügt -->
                        </div>
                
                        <!-- BEARBEITUNGS-MODUS (EDIT) -->
                        <div id="woEditDetailsContainer" class="hidden space-y-4 mt-3 pt-3 border-t border-slate-200/60">
                            <div>
                                <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Beschreibung</label>
                                <textarea id="woEditDescription" class="w-full border-slate-300 rounded-md text-sm" rows="4"></textarea>
                            </div>
                            <!-- NEU: Abschluss-Daten bearbeiten (Nur für Completed Orders) -->
                            <div id="woEditCompletionContainer" class="hidden mb-4 p-3 bg-green-50 border border-green-200 rounded-md">
                                <label class="block text-xs font-bold text-green-800 uppercase mb-2">Abschlussdatum korrigieren</label>
                                <div class="flex items-center gap-2">
                                    <input type="date" id="woEditCompletedDate" class="border-green-300 rounded-md text-sm p-1 w-full text-green-900 focus:ring-green-500 focus:border-green-500">
                                    <select id="woEditCompletedShift" class="border-green-300 rounded-md text-sm p-1 bg-white text-green-900 focus:ring-green-500 focus:border-green-500">
                                        <option value="AM">AM</option>
                                        <option value="PM">PM</option>
                                    </select>
                                </div>
                                <p class="text-[10px] text-green-700 mt-1">Ändert die Historie und gibt blockierte Zeiten in der Timeline frei.</p>
                            </div>
                            <!-- Diese Felder sind nur für Issues & Actions relevant -->
                            <div id="woEditMetaFields" class="grid grid-cols-2 gap-4">
                                <div>
                                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Priorität</label>
                                    <select class="w-full border-slate-300 rounded-md text-sm bg-white" id="woEditPriority">
                                        <option value="Low">Niedrig</option>
                                        <option value="Medium">Mittel</option>
                                        <option value="High">Hoch</option>
                                        <option value="Critical">Kritisch</option>
                                    </select>
                                </div>
                                <div>
                                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Bereich</label>
                                    <select class="w-full border-slate-300 rounded-md text-sm bg-white" id="woEditCategory"></select>
                                </div>
                            </div>
                            <!-- NEUER BLOCK FÜR ASSET-BEARBEITUNG -->
                            <div id="woEditAssetContainer" class="hidden">
                                <label class="block text-xs font-bold text-slate-500 uppercase mb-2">Betroffene Assets</label>
                                <div class="relative">
                                    <div class="tag-input-wrapper" id="woEditAssetTags">
                                        <input type="text" id="woEditAssetInput" class="tag-input-field" placeholder="Asset hinzufügen...">
                                    </div>
                                    <div id="woEditAssetDropdown" class="tag-dropdown"></div>
                                </div>
                            </div>
                            <div id="woEditBlockerSettings" class="hidden mt-4 pt-4 border-t border-slate-100 bg-red-50/50 p-3 rounded-md">
                                <label class="block text-xs font-bold text-red-800 uppercase mb-3">Betriebs-Sperren & Sicherheit</label>
                                
                                <!-- 1. Unit Blocker -->
                                <label class="flex items-center gap-2 cursor-pointer mb-4">
                                    <input type="checkbox" id="woEditUnitBlocker" class="rounded border-red-300 text-red-600 focus:ring-red-500 w-4 h-4">
                                    <div>
                                        <span class="text-sm font-bold text-slate-700">Anlage sperren (Unit Blocker)</span>
                                        <p class="text-[10px] text-slate-500">Raum wird im 3D-Modell rot markiert.</p>
                                    </div>
                                </label>
                            
                                <!-- 2. Hardware Blocker Liste -->
                                <div>
                                    <span class="text-xs font-semibold text-slate-600 block mb-1">Gesperrte Hardware auswählen:</span>
                                    <div id="woEditBlockedAssetsList" class="space-y-1 max-h-32 overflow-y-auto custom-scrollbar border border-slate-200 bg-white rounded p-2">
                                        <!-- Wird per JS gefüllt -->
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div id="woChecklistSection"><h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3">Checkliste</h3><div id="woChecklistContainer" class="space-y-2"></div></div>
                <div class="grid grid-cols-2 gap-6"><div id="woConsumablesSection"><h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Material</h3><div id="woConsumables" class="flex flex-wrap gap-2 text-sm text-slate-600"></div></div><div><h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">Dokumente</h3><div id="woAttachmentsView" class="space-y-2"></div></div></div>
            </div>
            <!-- RECHTE SPALTE bleibt unverändert... -->
            <div class="w-5/12 flex flex-col bg-slate-50">
                 <div id="woQuickActions" class="p-3 border-b border-slate-200 bg-white flex items-center justify-between gap-2 shadow-sm z-10"><div class="flex gap-2 w-full"><button id="btnTriage" onclick="openTriageModal()" class="hidden flex-1 bg-indigo-600 text-white px-3 py-2 rounded text-xs font-bold uppercase tracking-wide hover:bg-indigo-700 shadow-sm flex items-center justify-center gap-2 transition-colors"><span class="material-icons-outlined text-base">rule</span> Bewerten</button><button id="btnCreateIssue" onclick="createLinkedIssue()" class="flex-1 bg-amber-50 text-amber-700 border border-amber-200 px-3 py-2 rounded text-xs font-bold uppercase tracking-wide hover:bg-amber-100 flex items-center justify-center gap-2 transition-colors"><span class="material-icons-outlined text-base">report_problem</span> Issue</button><button id="btnCreateAction" onclick="createLinkedAction()" class="flex-1 bg-blue-50 text-blue-700 border border-blue-200 px-3 py-2 rounded text-xs font-bold uppercase tracking-wide hover:bg-blue-100 flex items-center justify-center gap-2 transition-colors"><span class="material-icons-outlined text-base">build_circle</span> Maßnahme</button></div></div>
                 <div class="flex-grow overflow-y-auto custom-scrollbar p-4 space-y-4" id="auditTrailContainer"></div>
                 <div class="p-4 bg-white border-t border-slate-200 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10">
                    <div class="mb-3"><textarea id="logNoteInput" class="w-full border-slate-300 rounded-lg text-sm focus:ring-primary focus:border-primary bg-slate-50 p-3 min-h-[80px]" placeholder="Kommentar oder Ergebnis protokollieren..."></textarea><div class="flex justify-between items-center mt-2"><button onclick="document.getElementById('logFileInput').click()" class="text-xs text-slate-400 hover:text-primary flex items-center gap-1 transition-colors"><span class="material-icons-outlined text-base">attach_file</span> Datei</button><input type="file" id="logFileInput" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt" class="hidden" onchange="addLogAttachments(this)"><button onclick="addLogEntry()" class="bg-slate-800 text-white px-4 py-1.5 rounded-md text-xs font-bold uppercase tracking-wide hover:bg-slate-700 transition-colors">Senden</button></div></div>
                    <!-- NEUE BUTTONS FÜR BEARBEITUNGSMODUS -->
                    <div id="woEditButtons" class="hidden pt-3 border-t border-slate-100 grid grid-cols-2 gap-4">
                        <button onclick="switchToWoViewMode()" class="w-full py-3 rounded-lg border-2 border-slate-200 bg-slate-50 text-slate-700 font-bold uppercase tracking-wider flex items-center justify-center gap-2 hover:bg-slate-100">
                            <span class="material-icons-outlined text-lg">cancel</span> Abbrechen
                        </button>
                        <button onclick="saveWorkOrderChanges()" class="w-full py-3 rounded-lg border-2 border-blue-100 bg-blue-50 text-blue-700 font-bold uppercase tracking-wider flex items-center justify-center gap-2 hover:bg-blue-100">
                            <span class="material-icons-outlined text-lg">save</span> Speichern
                        </button>
                    </div>
                    <div id="statusActions" class="pt-3 border-t border-slate-100 grid grid-cols-2 gap-4"></div>
                 </div>
            </div>
        </div>
    </div>
</div>

<!-- DEFINITION MODAL -->
<div id="definitionModal" class="modal hidden fixed inset-0 z-50 flex items-center justify-center bg-gray-900 bg-opacity-50">
    <div class="modal-backdrop" onclick="closeDefinitionModal()"></div>
    <div class="modal-content max-w-4xl flex flex-col max-h-[90vh]">
        <div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-lg flex-shrink-0">
            <div class="flex items-center gap-3"><h3 class="text-lg font-bold text-slate-800" id="defModalTitle">Wartungsvorlage</h3><div id="defHeaderBadgeView" class="hidden"></div></div>
            <div class="flex items-center gap-4">
                <label id="defHeaderToggleEdit" class="inline-flex items-center cursor-pointer hidden"><input type="checkbox" id="defActive" class="sr-only peer" checked><div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div><span class="ms-3 text-sm font-medium text-gray-700">Aktiv</span></label>
                <button id="btnSwitchToDefEdit" onclick="switchToDefEditMode()" class="text-slate-400 hover:text-blue-600 p-1.5 rounded-full hover:bg-slate-100 transition-colors" title="Bearbeiten"><span class="material-icons-outlined">edit</span></button>
                <button onclick="closeDefinitionModal()" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-full hover:bg-slate-100 transition-colors"><span class="material-icons-outlined">close</span></button>
            </div>
        </div>
        <div class="p-6 overflow-y-auto custom-scrollbar bg-slate-50 flex-grow">
            <div id="defViewContainer" class="space-y-6">
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm grid grid-cols-12 gap-4 text-sm">
                    <div class="col-span-12 flex justify-between border-b pb-2 mb-1"><h4 class="font-bold text-slate-800">Stammdaten</h4><span id="defIdView" class="font-mono text-xs text-slate-400"></span></div>
                    <div class="col-span-8"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Aufgabe</p><p id="defTaskView" class="font-medium text-slate-800 text-base"></p></div>
                    <div class="col-span-4"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Bereich</p><p id="defTopicView" class="text-slate-700"></p></div>
                    <div class="col-span-4"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Nächste Fälligkeit</p><p id="defStartDateView" class="text-slate-700 font-medium"></p></div>
                    <div class="col-span-4"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Intervall</p><p id="defIntervalView" class="text-slate-700"></p></div>
                    <div class="col-span-4"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Dauer</p><p id="defDurationView" class="text-slate-700"></p></div>
                    <div id="defFixedRhythmSection" class="col-span-4 hidden"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Fester Rhythmus</p><p id="defFixedRhythmView" class="text-slate-700 font-medium"></p></div>
                    <div class="col-span-12 border-t border-slate-100 pt-3 mt-1 grid grid-cols-2 gap-4"><div><p class="text-xs font-bold text-slate-400 uppercase mb-1">Ausführung durch</p><p id="defExternalCompanyView" class="text-slate-700 font-medium"></p></div><div><p class="text-xs font-bold text-slate-400 uppercase mb-1">Review</p><p id="defReviewView" class="text-slate-700"></p></div></div>
                    <div class="col-span-12 border-t border-slate-100 pt-3 mt-1"><p class="text-xs font-bold text-slate-400 uppercase mb-2">Tags</p><div id="defTagsView" class="flex flex-wrap gap-1 min-h-[24px]"><span class="text-xs text-slate-400 italic">-</span></div></div>
                </div>
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm grid grid-cols-12 gap-4 text-sm">
                    <div class="col-span-12"><h4 class="font-bold text-slate-800 mb-2 border-b pb-1">Ort & Objekt</h4></div>
                    <div class="col-span-4"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Prüfstand</p><p id="defTestbenchView" class="text-slate-700"></p></div>
                    <div class="col-span-2"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Index</p><p id="defIndexView" class="text-slate-700"></p></div>
                    <div class="col-span-6"><p class="text-xs font-bold text-slate-400 uppercase mb-1">Assets</p><div id="defAssetsView" class="flex flex-wrap gap-1 mt-1"></div></div>
                </div>
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm space-y-4 text-sm">
                    <h4 class="font-bold text-slate-800 mb-2 border-b pb-1">Durchführung</h4>
                    <div><p class="text-xs font-bold text-slate-400 uppercase mb-2">Checkliste</p><ul id="defChecklistView" class="space-y-1 pl-1"></ul></div>
                    <div><p class="text-xs font-bold text-slate-400 uppercase mb-1">Beschreibung</p><div id="defDescView" class="text-slate-700 whitespace-pre-wrap bg-slate-50 p-2 rounded border border-slate-100"></div></div>
                    <div><p class="text-xs font-bold text-slate-400 uppercase mb-1">Verbrauchsmaterial</p><div id="defConsumablesView" class="flex flex-wrap gap-1 mt-1"></div></div>
                </div>
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm"><label class="block text-xs font-bold text-slate-500 uppercase mb-3">Anhänge</label><div id="defAttachmentsView" class="grid grid-cols-2 gap-4"></div></div>
            </div>
            <form id="definitionForm" class="space-y-6 hidden">
                <input type="hidden" id="defId">
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm grid grid-cols-12 gap-4">
                    <div class="col-span-12"><h4 class="text-sm font-bold text-slate-800 mb-2 border-b pb-1">Stammdaten Bearbeiten</h4></div>
                    <div class="col-span-8"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Aufgabe / Titel</label><input class="w-full border-slate-300 rounded-md text-sm" id="defTask" required></div>
                    <div class="col-span-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Bereich</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="defTopic"></select></div>
                    <div class="col-span-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Startdatum (Erste Fälligkeit)</label><input type="date" class="w-full border-slate-300 rounded-md text-sm" id="defStartDate" required></div>
                    <div class="col-span-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Intervall</label><div class="flex gap-2"><input type="number" class="w-full border-slate-300 rounded-md text-sm" id="defIntVal" required><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="defIntUnit" onchange="updateIntervalTypeUI()"><option>Monate</option><option>Jahre</option><option>Wochen</option></select></div></div>
                    <div class="col-span-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Geplante Dauer</label><input type="text" class="w-full border-slate-300 rounded-md text-sm" id="defDuration" placeholder="z.B. 2 Std"></div>
                    <div class="col-span-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Rhythmus-Typ</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="defIntervalType" onchange="updateIntervalTypeUI()"><option value="rolling">Rollierend (ab Abschluss)</option><option value="fixed">Fester Rhythmus (Kalender)</option></select></div>
                    <div class="col-span-8 hidden" id="defFixedRhythmContainer"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Rhythmus-Punkt</label><div id="defFixedWeekdayRow" class="hidden"><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="defFixedWeekday"><option value="1">Montag</option><option value="2">Dienstag</option><option value="3">Mittwoch</option><option value="4">Donnerstag</option><option value="5">Freitag</option><option value="6">Samstag</option><option value="0">Sonntag</option></select></div><div id="defFixedDayOfMonthRow" class="hidden"><div class="flex items-center gap-2"><span class="text-sm text-slate-600">Am</span><input type="number" class="w-20 border-slate-300 rounded-md text-sm text-center" id="defFixedDayOfMonth" min="1" max="28" placeholder="1" value="1"><span class="text-sm text-slate-600">. des Monats</span></div></div><div id="defFixedYearRow" class="hidden"><div class="flex items-center gap-2"><span class="text-sm text-slate-600">Am</span><input type="number" class="w-20 border-slate-300 rounded-md text-sm text-center" id="defFixedDay" min="1" max="28" placeholder="1" value="1"><span class="text-sm text-slate-600">.</span><select class="border-slate-300 rounded-md text-sm bg-white" id="defFixedMonth"><option value="1">Januar</option><option value="2">Februar</option><option value="3">März</option><option value="4">April</option><option value="5">Mai</option><option value="6">Juni</option><option value="7">Juli</option><option value="8">August</option><option value="9">September</option><option value="10">Oktober</option><option value="11">November</option><option value="12">Dezember</option></select></div></div><p class="text-[10px] text-slate-400 mt-1">Aufträge werden stets auf diesen Kalender-Punkt gesetzt, unabhängig vom Abschlussdatum.</p></div>
                    <div class="col-span-12 border-t border-slate-100 pt-3 mt-1"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Ausführung durch</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="defExternalCompany"><option value="">Intern / Internes Team</option></select><p class="text-[10px] text-slate-400 mt-1">Wählen Sie eine externe Firma, falls die Wartung ausgelagert wird.</p></div>
                    <div class="col-span-12 border-t border-slate-100 pt-3 mt-1"><label class="flex items-center gap-2 text-xs font-bold uppercase text-slate-600 cursor-pointer"><input type="checkbox" id="defReqReview" class="rounded border-slate-300 text-purple-600 focus:ring-purple-500" onchange="toggleReviewerInput()"> 4-Augen-Prinzip erforderlich (Review)</label><div id="defReviewerBox" class="hidden mt-2 pl-6"><label class="block text-xs text-slate-400 mb-1">Designierter Prüfer (ID) - Optional</label><input id="defReviewer" class="w-1/2 border-slate-300 rounded-md text-sm" placeholder="z.B. mmuster"></div></div>
                    <div class="col-span-12 border-t border-slate-100 pt-3 mt-1"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Tags / Schlagwörter</label><div class="flex gap-2 mb-2"><input class="flex-1 border-slate-300 rounded-md text-sm" id="defTagInput" placeholder="#hydraulik, #extern ..." onkeydown="if(event.key==='Enter'||event.key===','){event.preventDefault();addTag();}"><button type="button" onclick="addTag()" class="bg-slate-100 hover:bg-slate-200 px-3 rounded border border-slate-300 text-slate-600"><span class="material-icons-outlined text-base align-middle">add</span></button></div><div id="defTagContainer" class="flex flex-wrap min-h-[24px] gap-1"></div><p class="text-[10px] text-slate-400 mt-1">Enter oder Komma bestätigt. Tags sind per Freitextsuche auffindbar.</p></div>
                </div>
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm grid grid-cols-12 gap-4">
                    <div class="col-span-12"><h4 class="text-sm font-bold text-slate-800 mb-2 border-b pb-1">Ort & Objekt</h4></div>
                    <div class="col-span-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Prüfstand</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="defTestbench"></select></div>
                    <div class="col-span-2">
                        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Betriebseinheit</label>
                        <input list="facilityUnitList" class="w-full border-slate-300 rounded-md text-sm" id="defIndex" onchange="onFacilityUnitSelected(this, 'defFacilityUnitId')"> <!-- onchange NEU -->
                        <input type="hidden" id="defFacilityUnitId"> <!-- NEU: Hidden ID Field -->
                        <datalist id="facilityUnitList"></datalist>
                    </div>
                    <div class="col-span-6"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Wartungsobjekte (Assets)</label><div class="flex gap-2 mb-2"><input list="hardwareList" class="flex-1 border-slate-300 rounded-md text-sm" id="defAssetInput" placeholder="Hardware hinzufügen..."><datalist id="hardwareList"></datalist><button type="button" onclick="addAsset()" class="bg-slate-100 hover:bg-slate-200 px-3 rounded border border-slate-300 text-slate-600"><span class="material-icons-outlined text-base align-middle">add</span></button></div><div id="defAssetContainer" class="flex flex-wrap min-h-[24px]"></div></div>
                </div>
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm space-y-4">
                    <h4 class="text-sm font-bold text-slate-800 mb-2 border-b pb-1">Durchführung</h4>
                    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-2">Checkliste (Drag & Drop)</label><div class="flex gap-2 mb-2 items-center flex-wrap" id="newChecklistRow"><input type="text" id="newChecklistItem" class="flex-1 border-slate-300 rounded-md text-sm min-w-[150px]" placeholder="Neuer Schritt..." onkeydown="if(event.key==='Enter'){event.preventDefault();addChecklistItem();}"><button type="button" id="btnToggleMeasure" onclick="toggleNewItemMeasure()" title="Messwert-Eingabefeld hinzufügen" class="flex items-center gap-1 px-2 py-1 rounded border border-slate-300 text-xs text-slate-500 hover:bg-blue-50 hover:border-blue-400 hover:text-blue-600 transition-colors bg-white whitespace-nowrap"><span class="material-icons-outlined text-sm">speed</span><span>Messwert</span></button><input type="text" id="newChecklistUnit" class="w-20 border-slate-300 rounded-md text-sm hidden" placeholder="Einheit…" maxlength="10" title="Einheit (z.B. bar, °C, mm)"><button type="button" onclick="addChecklistItem()" class="bg-slate-100 hover:bg-slate-200 text-slate-600 px-3 py-1 rounded border border-slate-300"><span class="material-icons-outlined text-base align-middle">add</span></button></div><ul id="defChecklist" class="space-y-1 bg-slate-50 p-2 rounded border border-slate-100 min-h-[3rem]"></ul></div>
                    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Zusätzliche Beschreibung / Hinweise</label><textarea class="w-full border-slate-300 rounded-md text-sm h-20" id="defDesc" placeholder="Allgemeine Hinweise, Sicherheit..."></textarea></div>
                    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Verbrauchsmaterial / Consumables</label><div class="flex gap-2 mb-2"><input class="flex-1 border-slate-300 rounded-md text-sm" id="defConsumableInput" placeholder="Material hinzufügen..."><button type="button" onclick="addConsumable()" class="bg-slate-100 hover:bg-slate-200 px-3 rounded border border-slate-300 text-slate-600"><span class="material-icons-outlined text-base align-middle">add</span></button></div><div id="defConsumablesContainer" class="flex flex-wrap min-h-[24px]"></div></div>
                </div>
                <div class="bg-white p-5 rounded-lg border border-slate-200 shadow-sm"><label class="block text-xs font-bold text-slate-500 uppercase mb-3">Anhänge & Dokumentation</label><div class="grid grid-cols-2 gap-4"><div><label class="cursor-pointer flex items-center justify-center gap-2 p-3 border-2 border-dashed border-slate-300 rounded-lg hover:border-primary hover:bg-blue-50 transition-colors text-slate-500"><span class="material-icons-outlined">add_photo_alternate</span> Bilder hochladen <input type="file" class="hidden" id="defImageInput" multiple accept="image/*"></label><div id="defImagePreview" class="mt-2 space-y-1"></div></div><div><label class="cursor-pointer flex items-center justify-center gap-2 p-3 border-2 border-dashed border-slate-300 rounded-lg hover:border-primary hover:bg-blue-50 transition-colors text-slate-500"><span class="material-icons-outlined">description</span> Dokumente hochladen <input type="file" class="hidden" id="defDocInput" multiple></label><div id="defDocPreview" class="mt-2 space-y-1"></div></div></div></div>
            </form>
        </div>
        <div class="p-4 border-t border-slate-200 bg-white flex justify-end gap-3 rounded-b-lg flex-shrink-0">
            <div id="defFooterView" class="flex w-full justify-end"><button class="px-4 py-2 text-sm font-medium text-white bg-slate-800 hover:bg-slate-700 rounded shadow-sm" onclick="closeDefinitionModal()">Schließen</button></div>
            <div id="defFooterEdit" class="flex gap-3 hidden w-full justify-between">
                 <button id="btnDefDelete" type="button" class="px-4 py-2 text-red-600 bg-red-50 border border-red-200 rounded hover:bg-red-100 text-sm font-medium flex items-center gap-1" onclick="deleteDefinitionFromModal()"><span class="material-icons-outlined text-sm">delete</span> Löschen</button>
                 <div class="flex gap-3 ml-auto"><button class="px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded border border-slate-300" onclick="cancelDefEdit()">Abbrechen</button><button class="px-4 py-2 text-sm bg-primary text-white rounded hover:bg-blue-700 font-medium shadow-sm" onclick="saveDefinition()">Speichern</button></div>
            </div>
        </div>
    </div>
</div>

<!-- HISTORY & CREATE MODALS -->
<div id="historyListModal" class="modal hidden fixed inset-0 z-50 flex items-center justify-center bg-gray-900 bg-opacity-50 backdrop-blur-sm"><div class="modal-backdrop" onclick="closeHistoryListModal()"></div><div class="modal-content max-w-4xl w-full flex flex-col max-h-[85vh]"><div class="flex-shrink-0 px-6 py-5 border-b border-slate-200 bg-white rounded-t-lg flex justify-between items-start"><div class="flex-grow pr-4"> <!-- Flex-grow damit er Platz nimmt, aber Button rechts bleibt -->
    <!-- Container für Titel und Badge in einer Zeile -->
    <div class="flex items-center flex-wrap gap-2" id="historyTitleContainer">
        <h3 class="text-xl font-bold text-slate-800" id="historyModalTitle">Ausführungshistorie</h3>
        <div id="historyStatsBadge"></div> <!-- Platzhalter für die Pille -->
    </div>
    <p class="text-sm text-slate-500 mt-1">Ausführungshistorie</p>
</div><button onclick="closeHistoryListModal()" class="text-slate-400 hover:text-slate-600 p-1 bg-slate-50 hover:bg-slate-100 rounded-full transition-colors"><span class="material-icons-outlined">close</span></button></div><div class="flex-grow overflow-y-auto custom-scrollbar bg-slate-50 p-6"><div class="bg-white rounded-lg border border-slate-200 shadow-sm overflow-hidden"><table class="w-full text-sm text-left"><thead class="bg-slate-50 text-slate-500 text-xs font-bold uppercase tracking-wider border-b border-slate-200"><tr><th class="px-6 py-3">Status / ID</th><th class="px-6 py-3">Abschlussdatum</th><th class="px-6 py-3">Durchgeführt von</th><th class="px-6 py-3 text-right">Aktion</th></tr></thead><tbody id="historyListBody" class="divide-y divide-slate-100"></tbody></table></div></div><div class="flex-shrink-0 px-6 py-4 border-t border-slate-200 bg-white rounded-b-lg flex justify-end"><button onclick="closeHistoryListModal()" class="px-4 py-2 bg-slate-100 text-slate-700 font-medium rounded-lg hover:bg-slate-200 transition-colors">Schließen</button></div></div></div>

<!-- TRIAGE MODAL -->
<div id="triageModal" class="modal hidden fixed inset-0 z-[70] flex items-center justify-center bg-gray-900 bg-opacity-50 backdrop-blur-sm">
    <div class="modal-backdrop" onclick="closeTriageModal()"></div>
    <div class="modal-content max-w-lg w-full transform transition-all scale-100 rounded-xl shadow-2xl">
        <div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-xl">
            <h3 class="text-xl font-bold text-slate-800 flex items-center gap-2">
                <span class="material-icons-outlined text-blue-600">rule</span> Issue Bewerten
            </h3>
            <button onclick="closeTriageModal()" class="text-slate-400 hover:text-slate-600 transition-colors"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6 bg-white space-y-6">
            <div class="bg-slate-50 p-4 rounded-lg border border-slate-200 text-sm">
                <p class="text-xs font-bold text-slate-400 uppercase mb-1">Betroffenes Issue</p>
                <p class="font-medium text-slate-800" id="triageIssueTitle"></p>
                <p class="text-slate-500 mt-1 truncate" id="triageIssueDesc"></p>
            </div>
            <form id="triageForm" class="space-y-6">
                <!-- 1. Priorität -->
                <div>
                    <label class="block text-sm font-bold text-slate-700 mb-2">Priorität festlegen *</label>
                    <div class="grid grid-cols-4 gap-2">
                        <label class="cursor-pointer"><input type="radio" name="triagePriority" value="Low" class="peer sr-only"><div class="text-center py-2 border rounded-md text-xs font-bold uppercase tracking-wider text-slate-600 bg-white hover:bg-slate-50 peer-checked:bg-slate-100 peer-checked:text-slate-800 peer-checked:border-slate-400 peer-checked:ring-1 peer-checked:ring-slate-400 transition-all">Niedrig</div></label>
                        <label class="cursor-pointer"><input type="radio" name="triagePriority" value="Medium" class="peer sr-only"><div class="text-center py-2 border rounded-md text-xs font-bold uppercase tracking-wider text-yellow-600 bg-white hover:bg-yellow-50 peer-checked:bg-yellow-50 peer-checked:border-yellow-400 peer-checked:ring-1 peer-checked:ring-yellow-400 transition-all">Mittel</div></label>
                        <label class="cursor-pointer"><input type="radio" name="triagePriority" value="High" class="peer sr-only"><div class="text-center py-2 border rounded-md text-xs font-bold uppercase tracking-wider text-orange-600 bg-white hover:bg-orange-50 peer-checked:bg-orange-50 peer-checked:border-orange-400 peer-checked:ring-1 peer-checked:ring-orange-400 transition-all">Hoch</div></label>
                        <label class="cursor-pointer"><input type="radio" name="triagePriority" value="Critical" class="peer sr-only"><div class="text-center py-2 border rounded-md text-xs font-bold uppercase tracking-wider text-red-600 bg-white hover:bg-red-50 peer-checked:bg-red-50 peer-checked:border-red-400 peer-checked:ring-1 peer-checked:ring-red-400 transition-all">Kritisch</div></label>
                    </div>
                </div>
            
                <hr class="border-slate-200">
            
                <!-- 2. Anlagen-Sperre -->
                <div>
                    <label class="flex items-start gap-3 p-3 rounded-lg border border-red-100 bg-red-50 cursor-pointer hover:bg-red-100 transition-colors">
                        <input id="triageUnitBlocker" type="checkbox" class="mt-1 w-5 h-5 text-red-600 border-gray-300 rounded focus:ring-red-500">
                        <div>
                            <span class="block font-bold text-red-800 text-sm">Betriebseinheit sperren (Anlagen-Blocker)</span>
                            <span class="block text-xs text-red-600 mt-1">Die gesamte Anlage/Testzelle ist nicht betriebsbereit (Anzeige im 3D-Viewer).</span>
                        </div>
                    </label>
                </div>
            
                <!-- 3. Hardware-Sperre (Dynamisch) -->
                <div id="triageAssetSection" class="hidden">
                    <label class="block text-sm font-bold text-slate-700 mb-2">Betroffene Hardware sperren</label>
                    <p class="text-xs text-slate-500 mb-2">Markierte Geräte werden im Inventar auf "Maintenance" gesetzt.</p>
                    <div id="triageAssetList" class="space-y-2 max-h-40 overflow-y-auto custom-scrollbar border border-slate-200 rounded-md p-2 bg-slate-50">
                        <!-- Wird per JS gefüllt -->
                    </div>
                </div>
            </form>
        </div>
        <div class="p-4 border-t border-slate-200 bg-white rounded-b-xl flex justify-end gap-3">
            <button onclick="closeTriageModal()" class="px-4 py-2 text-sm font-medium text-slate-600 hover:bg-slate-100 rounded-lg transition-colors">Abbrechen</button>
            <button onclick="submitTriage()" class="px-4 py-2 text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-sm transition-colors flex items-center gap-2"><span class="material-icons-outlined text-base">save</span> Speichern</button>
        </div>
    </div>
</div>

<!-- CREATE ISSUE MODAL -->
<div id="createIssueModal" class="modal hidden fixed inset-0 z-[60] flex items-center justify-center bg-gray-900 bg-opacity-50"><div class="modal-backdrop" onclick="closeCreateIssueModal()"></div><div class="modal-content max-w-2xl"><div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-lg"><h3 class="text-xl font-bold text-slate-800 flex items-center gap-2"><span class="material-icons-outlined text-yellow-600">report_problem</span> Mangel / Issue melden</h3><button onclick="closeCreateIssueModal()" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button></div><div class="p-6 overflow-y-auto custom-scrollbar bg-slate-50 max-h-[70vh]"><form id="createIssueForm" class="space-y-5">
    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Titel (Kurzbeschreibung)*</label><input class="w-full border-slate-300 rounded-md text-sm focus:ring-yellow-500 focus:border-yellow-500" id="issueTitle" required placeholder="z.B. Leckage an Hydraulikpumpe"></div>
    <div id="editIssueMetaContainer" class="hidden grid grid-cols-2 gap-4 p-4 bg-white border border-slate-200 rounded-lg">
        <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Priorität</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="issuePriority"><option value="Low">Niedrig</option><option value="Medium">Mittel</option><option value="High">Hoch</option><option value="Critical">Kritisch</option></select></div>
        <div class="flex items-center mt-6"><input type="checkbox" id="issueBlocker" class="rounded border-red-300 text-red-600 focus:ring-red-500 w-5 h-5 mr-2"><label for="issueBlocker" class="text-sm font-medium text-red-800 cursor-pointer">Betriebsblocker</label></div>
    </div>
    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Bereich</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="issueCategory"></select></div>
    <div id="issueAssetContainer" class="hidden mt-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Betroffene Assets (Mehrfachauswahl)</label><div id="issueAssetList" class="border border-slate-300 rounded-md bg-white p-2 max-h-32 overflow-y-auto space-y-1"></div><p class="text-[10px] text-slate-400 mt-1">Ausgewählte Geräte werden als "Defekt" markiert.</p></div>
    <div class="mt-4">
        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Betriebsbereich (Anlage)</label>
        <input list="facilityUnitList" class="w-full border-slate-300 rounded-md text-sm" id="issuePlantIndex" onchange="onFacilityUnitSelected(this, 'issueFacilityUnitId')">
        <input type="hidden" id="issueFacilityUnitId"> 
    </div>
    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Beschreibung</label><textarea class="w-full border-slate-300 rounded-md text-sm" id="issueDescription" rows="4"></textarea></div>
    <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Foto / Anhang</label><input type="file" id="issueAttachment" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-yellow-50 file:text-yellow-700 hover:file:bg-yellow-100"></div></form></div><div class="p-4 border-t border-slate-200 bg-white flex justify-end gap-3 rounded-b-lg">
        <button class="px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded" onclick="closeCreateIssueModal()">Abbrechen</button>
        <button id="btnSaveIssue" class="px-4 py-2 text-sm bg-yellow-600 text-white rounded hover:bg-yellow-700 font-medium shadow-sm flex items-center gap-2" onclick="submitCreateIssue()"><span class="material-icons-outlined text-sm">save</span> Mangel anlegen</button>
    </div></div></div>

<!-- CREATE ACTION MODAL -->
<div id="createActionModal" class="modal hidden fixed inset-0 z-[60] flex items-center justify-center bg-gray-900 bg-opacity-50"><div class="modal-backdrop" onclick="closeCreateActionModal()"></div><div class="modal-content max-w-2xl"><div class="flex justify-between items-center p-6 border-b border-slate-200 bg-white rounded-t-lg"><h3 class="text-xl font-bold text-slate-800 flex items-center gap-2"><span class="material-icons-outlined text-green-600">build_circle</span> Maßnahme planen</h3><button onclick="closeCreateActionModal()" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button></div><div class="p-6 overflow-y-auto custom-scrollbar bg-slate-50 max-h-[70vh]"><form id="createActionForm" class="space-y-5"><div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Titel der Maßnahme*</label><input class="w-full border-slate-300 rounded-md text-sm focus:ring-green-500 focus:border-green-500" id="actionTitle" required></div><div class="grid grid-cols-2 gap-4"><div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Fälligkeit</label><input type="date" class="w-full border-slate-300 rounded-md text-sm" id="actionDueDate"></div><div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Aufwand</label><input type="text" class="w-full border-slate-300 rounded-md text-sm" id="actionEffort"></div></div><div class="mt-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Bereich</label><select class="w-full border-slate-300 rounded-md text-sm bg-white" id="actionCategory"></select></div><div class="mt-4">
    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Betriebsbereich (Anlage)</label>
    <input list="facilityUnitList" class="w-full border-slate-300 rounded-md text-sm" id="actionPlantIndex" onchange="onFacilityUnitSelected(this, 'actionFacilityUnitId')">
    <input type="hidden" id="actionFacilityUnitId">
</div><div id="actionAssetContainer" class="hidden mt-4"><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Betroffene Assets (Mehrfachauswahl)</label><div id="actionAssetList" class="border border-slate-300 rounded-md bg-white p-2 max-h-32 overflow-y-auto space-y-1"></div><p class="text-[10px] text-slate-400 mt-1">Wählen Sie die Assets aus, an denen diese Maßnahme durchgeführt wird.</p></div><div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Details</label><textarea class="w-full border-slate-300 rounded-md text-sm" id="actionInstructions" rows="4"></textarea></div><div class="bg-blue-50 p-3 rounded border border-blue-100"><label class="block text-xs font-bold text-blue-800 uppercase mb-1">Material</label><textarea class="w-full border-blue-200 rounded-md text-sm focus:border-blue-400 focus:ring-blue-400" id="actionSpareParts" rows="2"></textarea></div></form></div><div class="p-4 border-t border-slate-200 bg-white flex justify-end gap-3 rounded-b-lg">
        <button class="px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded" onclick="closeCreateActionModal()">Abbrechen</button>
        <button id="btnSaveAction" class="px-4 py-2 text-sm bg-green-600 text-white rounded hover:bg-green-700 font-medium shadow-sm flex items-center gap-2" onclick="submitCreateAction()"><span class="material-icons-outlined text-sm">save</span> Speichern</button>
    </div></div></div>

<!-- LIGHTBOX MODAL -->
<div id="lightboxModal" class="modal hidden fixed inset-0 z-[9999] bg-black bg-opacity-90 flex items-center justify-center backdrop-blur-sm transition-opacity duration-300" onclick="closeLightbox()">
    <div class="relative max-w-full max-h-full p-4 flex flex-col items-center justify-center">
        <!-- Bild Container -->
        <img id="lightboxImage" src="" class="max-h-[85vh] max-w-[90vw] object-contain rounded shadow-2xl cursor-default" onclick="event.stopPropagation()">
        
        <!-- Schließen Button (schwebend) -->
        <button class="absolute top-6 right-6 text-white bg-white/10 hover:bg-white/20 rounded-full p-2 transition-colors backdrop-blur-md" onclick="closeLightbox()">
            <span class="material-icons-outlined text-2xl">close</span>
        </button>
        
        <!-- Titel / Dateiname (Optional) -->
        <p id="lightboxCaption" class="mt-4 text-slate-200 text-sm font-medium tracking-wide bg-black/50 px-4 py-1 rounded-full"></p>
    </div>
</div>
<div id="assetHistoryModal" class="modal hidden fixed inset-0 z-[80] flex items-center justify-center bg-gray-900 bg-opacity-60 backdrop-blur-sm">
    <div class="modal-backdrop" onclick="closeAssetHistoryModal()"></div>
    <div class="modal-content max-w-2xl w-full flex flex-col max-h-[85vh] bg-white rounded-xl shadow-2xl">
        <div class="flex justify-between items-center p-6 border-b border-slate-100 bg-white rounded-t-xl z-10">
            <div>
                <h3 class="text-xl font-bold text-slate-800" id="assetHistoryTitle">Asset Historie</h3>
                <p class="text-sm text-slate-500 mt-1" id="assetHistorySubtitle">Lade Daten...</p>
            </div>
            <button onclick="closeAssetHistoryModal()" class="text-slate-400 hover:text-slate-600 p-2 hover:bg-slate-50 rounded-full transition-colors"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6 overflow-y-auto custom-scrollbar bg-slate-50/50 flex-grow">
            <!-- Timeline Container -->
            <div id="assetHistoryTimeline" class="relative border-l-2 border-slate-200 ml-3 space-y-8 pb-4">
            </div>
        </div>
    </div>
</div>
<div id="currentUser" 
     data-name="{{ current_user_display_name }}" 
     data-id="{{ session.get('user_id', '').lower() }}"
     data-is-admin="{{ 'true' if session.get('is_admin') else 'false' }}"
     data-roles='{{ session.get("roles", []) | tojson }}' 
     class="hidden">
</div>
<script>

    let maintenanceOrders = {{ orders_json | safe }};
    let taskDefinitions = {{ definitions_json | safe }};
    let companies = {{ companies_json | safe }}; 
    let maintenanceLogs = [];

    if (!maintenanceOrders) maintenanceOrders = [];
    if (!taskDefinitions) taskDefinitions = [];
    if (!companies) companies = [];

    let hardwareInventoryList = [];
    let adHocContext = null;
    let isWorkOrderInEditMode = false;
    let currentAssetTags = [];
    let availableAssetOptions = [];
    let analysisCharts = {};
    let globalAssetStats = [];
    
    // Globale Variablen für Modal
    let currentDefImages = []; 
    let currentDefDocs = []; 
    let currentChecklist = []; 
    let currentDefAssets = []; 
    let currentDefConsumables = [];
    let currentDefTags = [];
    let sortableChecklist = null;
    let currentOrderId = null;
    
    const currentUser = "{{ current_user_display_name }}";
    const currentUserId = "{{ session.get('user_id', '').lower() }}";
    const currentUserIsAdmin = {{ 'true' if session.get('is_admin') else 'false' }};
    const currentUserRoles = {{ session.get('roles', []) | tojson }};
    const canEditMaintenance = !currentUserRoles.includes('TESTMECHANIC');
    const isTestMechanic = currentUserRoles.includes('TESTMECHANIC') && !currentUserIsAdmin;

    const allHardwareAssets = {{ hardware_assets_json | safe }};
    const serverPermissions = {{ permissions_json | safe }};

    // --- FILTER LOGIK & UI ---
    
    // 1. Panel Toggle
    function toggleFilterPanel(panelId, btn) {
        const panel = document.getElementById(panelId);
        if (panel.classList.contains('hidden')) {
            panel.classList.remove('hidden');
            btn.classList.add('bg-blue-50', 'text-blue-700', 'border-blue-200');
            btn.classList.remove('bg-white', 'text-slate-600', 'border-slate-200');
        } else {
            panel.classList.add('hidden');
            btn.classList.remove('bg-blue-50', 'text-blue-700', 'border-blue-200');
            btn.classList.add('bg-white', 'text-slate-600', 'border-slate-200');
        }
    }
    
    // 2. Dropdowns initialisieren (Wird bei DOMContentLoaded aufgerufen)
    function initFilterDropdowns() {
        
        // --- 1. BEREICHE (AREAS) ---
        const areaSet = new Set([
        ]);
    
        if (typeof taskDefinitions !== 'undefined') {
            taskDefinitions.forEach(def => { if (def.topic && def.topic.trim()) areaSet.add(def.topic.trim()); });
        }
        if (typeof maintenanceOrders !== 'undefined') {
            maintenanceOrders.forEach(ord => { 
                const val = ord.topic || ord.category;
                if (val && val.trim()) areaSet.add(val.trim()); 
            });
        }
        const areas = Array.from(areaSet).sort();
    
    
        // --- 2. BETRIEBSBEREICHE (ROOTS) ---
        const rootMap = new Map();
        if (typeof facilityUnitMap !== 'undefined') {
            Object.keys(facilityUnitMap).forEach(fullName => {
                const rootMatch = fullName.match(/^(\d+)\s+(.*)$/);
                if (rootMatch) {
                    rootMap.set(rootMatch[1], fullName); 
                } else {
                    const childMatch = fullName.match(/^(\d+)\./);
                    if (childMatch && !rootMap.has(childMatch[1])) {
                        rootMap.set(childMatch[1], `Bereich ${childMatch[1]}`);
                    }
                }
            });
        }
        const sortedRootIndices = Array.from(rootMap.keys()).sort((a,b) => parseInt(a) - parseInt(b));
    
    
        // --- 3. PRIORITÄTEN (PRIORITIES) ---
        // Defaults, damit die Reihenfolge stimmt
        const prioSet = new Set(["Critical", "High", "Medium", "Low", "OPEN"]);
        
        if (typeof maintenanceOrders !== 'undefined') {
            maintenanceOrders.forEach(ord => {
                if (ord.priority && ord.priority.trim()) {
                    prioSet.add(ord.priority.trim());
                }
            });
        }
    
        // Logische Sortierung (Kritisch oben) statt Alphabetisch
        const prioOrder = { "Critical": 0, "High": 1, "Medium": 2, "Low": 3, "OPEN": 4 };
        const priorities = Array.from(prioSet).sort((a, b) => {
            const valA = prioOrder[a] !== undefined ? prioOrder[a] : 99;
            const valB = prioOrder[b] !== undefined ? prioOrder[b] : 99;
            return valA - valB;
        });
    
    
        // --- 4. INTERVALLE (INTERVALS) ---
        const intervalSet = new Set(["Wochen", "Monate", "Jahre"]);
        
        if (typeof taskDefinitions !== 'undefined') {
            taskDefinitions.forEach(def => {
                if (def.intervalUnit && def.intervalUnit.trim()) {
                    intervalSet.add(def.intervalUnit.trim());
                }
            });
        }
        // Alphabetisch sortieren
        const intervals = Array.from(intervalSet).sort();
    
    
        // --- FILL HELPER ---
        const fill = (id, items, isRootMode=false) => {
            const el = document.getElementById(id);
            if(!el) return;
            el.innerHTML = el.options[0].outerHTML; // Reset keep first
            
            items.forEach(item => {
                let val = item;
                let text = item;
                if (isRootMode) {
                    val = item;
                    text = rootMap.get(item);
                }
                // Übersetzung für Priorities (Optional, falls gewünscht)
                // const trans = { "Critical": "Kritisch", "High": "Hoch", "Medium": "Mittel", "Low": "Niedrig", "OPEN": "Unbewertet" };
                // if (trans[text]) text = trans[text];
    
                const opt = document.createElement('option');
                opt.value = val;
                opt.textContent = text;
                el.appendChild(opt);
            });
        };
    
        // --- EXECUTE ---
        fill('filterAreaActive', areas);
        fill('filterAreaDef', areas);
        fill('filterPlantActive', sortedRootIndices, true);
        fill('filterPlantDef', sortedRootIndices, true);
        
        // Neue Felder füllen
        fill('filterPrioActive', priorities);
        fill('filterIntervalDef', intervals);
    }
    
    function toggleMaintenanceFilter(panelId, badgeId) {
        const panel = document.getElementById(panelId);
        if (!panel) return;
        panel.classList.toggle('hidden');
    }

    function updateFilterBadge(badgeId, ids, resetBtnId) {
        const badge = document.getElementById(badgeId);
        const resetBtn = resetBtnId ? document.getElementById(resetBtnId) : null;
        if (!badge) return;
        const count = ids.filter(id => { const el = document.getElementById(id); return el && el.value !== 'all'; }).length;
        badge.textContent = count;
        if (count > 0) { badge.classList.remove('hidden'); badge.classList.add('flex'); if (resetBtn) { resetBtn.classList.remove('hidden'); resetBtn.classList.add('flex'); } }
        else { badge.classList.add('hidden'); badge.classList.remove('flex'); if (resetBtn) { resetBtn.classList.add('hidden'); resetBtn.classList.remove('flex'); } }
    }

    function resetFiltersActive() {
        ['filterTypeActive','filterPrioActive','filterBlockerActive','filterAreaActive','filterPlantActive'].forEach(id => { const el = document.getElementById(id); if (el) el.value = 'all'; });
        const s = document.getElementById('searchActiveOrders'); if (s) s.value = '';
        applyFiltersActive();
    }

    function resetFiltersDef() {
        ['filterIntervalDef','filterAreaDef','filterPlantDef'].forEach(id => { const el = document.getElementById(id); if (el) el.value = 'all'; });
        const s = document.getElementById('searchDefinitions'); if (s) s.value = '';
        applyFiltersDefinitions();
    }

    // 3. Filter Logik für AKTIVE AUFTRÄGE
    function applyFiltersActive() {
        const term = document.getElementById('searchActiveOrders').value.toLowerCase();
        const typeVal = document.getElementById('filterTypeActive').value;
        const prioVal = document.getElementById('filterPrioActive').value;
        const blockerVal = document.getElementById('filterBlockerActive').value;
        const areaVal = document.getElementById('filterAreaActive').value;
        const plantVal = document.getElementById('filterPlantActive').value;
    
        // 1. Alles zurücksetzen (Initial verstecken)
        document.querySelectorAll('.task-group').forEach(g => g.classList.add('hidden'));
        document.querySelectorAll('[class*="child-container-"]').forEach(c => c.classList.add('hidden'));
        document.querySelectorAll('.task-row button .material-icons-outlined').forEach(icon => icon.classList.remove('rotate-90'));
    
        const matchedTaskIds = new Set();
        const visibleTaskIds = new Set(); // Enthält Treffer + deren Eltern
    
        // 2. Treffer ermitteln
        maintenanceOrders.forEach(order => {
            if (order.status === 'Completed') return;
    
            let isMatch = true;
    
            // Text-Suche
            if (term) {
                const text = `${order.id} ${order.task} ${order.description || ''} ${order.plantIndex || ''}`.toLowerCase();
                if (!text.includes(term)) isMatch = false;
            }
            // Typ-Filter
            if (isMatch && typeVal !== 'all' && order.type !== typeVal) isMatch = false;
            // Prio-Filter
            if (isMatch && prioVal !== 'all' && order.priority !== prioVal) isMatch = false;
            // Blocker-Filter (Anlage/Hardware)
            if (isMatch && blockerVal !== 'all') {
                const isUnit = order.isUnitBlocker === true || order.isUnitBlocker === "true";
                const isHw = order.blockedAssetIds && order.blockedAssetIds.length > 0;
                const isAny = isUnit || isHw || (order.isBlocker === true && !isUnit && !isHw);
                if (blockerVal === 'any' && !isAny) isMatch = false;
                else if (blockerVal === 'unit' && !isUnit) isMatch = false;
                else if (blockerVal === 'hardware' && !isHw) isMatch = false;
                else if (blockerVal === 'none' && isAny) isMatch = false;
            }
            // Bereichs-Filter
            if (isMatch && areaVal !== 'all') {
                const oArea = order.topic || order.category;
                if (oArea !== areaVal) isMatch = false;
            }
            // Anlagen-Filter (Root)
            if (isMatch && plantVal !== 'all') {
                const pIndex = order.plantIndex || '';
                if (!pIndex.startsWith(plantVal + '.') && !pIndex.startsWith(plantVal + ' ')) isMatch = false;
            }
    
            if (isMatch) {
                matchedTaskIds.add(order.id);
                
                // Den Pfad nach oben berechnen (mit Endlosschleifen-Schutz)
                let current = order;
                visibleTaskIds.add(current.id);
                const visitedParents = new Set([current.id]); // Sicherung
                
                while (current && (current.sourceTask || current.linkedIssue)) {
                    const parentId = current.sourceTask || current.linkedIssue;
                    
                    // Wenn wir diesen Parent schon hatten -> Zirkelbezug! Abbruch.
                    if (visitedParents.has(parentId)) {
                        console.warn("Zirkelbezug bei Auftrag entdeckt:", parentId);
                        break;
                    }
                    
                    visitedParents.add(parentId);
                    visibleTaskIds.add(parentId);
                    
                    current = maintenanceOrders.find(o => o.id === parentId);
                }
            }
        });
    
        // 3. DOM aktualisieren (Sichtbarkeit & Struktur)
        visibleTaskIds.forEach(id => {
            // Finde das task-group Element für diese ID
            const row = document.querySelector(`.task-row[onclick*="'${id}'"]`);
            if (row) {
                const group = row.closest('.task-group');
                if (group) group.classList.remove('hidden');
    
                // Wenn diese ID ein ELTER eines anderen sichtbaren Elements ist,
                // müssen wir seinen Kinder-Container öffnen
                const childrenContainer = document.querySelector(`.child-container-${id}`);
                if (childrenContainer) {
                    // Checken, ob mindestens eines der Kinder in visibleTaskIds ist
                    const hasVisibleChild = maintenanceOrders.some(o => 
                        (o.sourceTask === id || o.linkedIssue === id) && visibleTaskIds.has(o.id)
                    );
                    
                    if (hasVisibleChild) {
                        childrenContainer.classList.remove('hidden');
                        // Pfeil des Vaters drehen
                        const icon = row.querySelector('button .material-icons-outlined');
                        if (icon) icon.classList.add('rotate-90');
                    }
                }
            }
        });
    
        // 4. Kachel-Sichtbarkeit (Buckets) aktualisieren
        const bucketIds = ['groupCritical', 'groupOverdue', 'groupIssues', 'groupUpcoming', 'groupOutlook', 'groupFuture'];
        const hasAnyFilter = term || typeVal !== 'all' || prioVal !== 'all' || areaVal !== 'all' || plantVal !== 'all' || blockerVal !== 'all';
    
        bucketIds.forEach(bucketId => {
            const bucket = document.getElementById(bucketId);
            if (!bucket) return;
            const bodyId = bucketId.replace('group', 'body');
            const body = document.getElementById(bodyId);
            const visibleInBucket = body.querySelectorAll('.task-group:not(.hidden)');
    
            if (visibleInBucket.length > 0) {
                bucket.classList.remove('hidden');
                if (hasAnyFilter) {
                    body.classList.remove('hidden');
                    const icon = bucket.querySelector('button .material-icons-outlined:last-child');
                    if (icon) icon.classList.remove('rotate-180');
                }
            } else {
                bucket.classList.add('hidden');
            }
        });
    
        // 5. Chips Styling
        const chipIds = ['filterTypeActive', 'filterPrioActive', 'filterBlockerActive', 'filterAreaActive', 'filterPlantActive'];
        chipIds.forEach(id => {
            const el = document.getElementById(id);
            if (el && el.value !== 'all') {
                el.classList.add('bg-blue-50', 'text-blue-700', 'border-blue-200', 'font-bold');
            } else if (el) {
                el.classList.remove('bg-blue-50', 'text-blue-700', 'border-blue-200', 'font-bold');
            }
        });
        updateFilterBadge('filterBadgeActive', chipIds, 'filterResetActive');
    }
    
    // 4. Filter Logik für DEFINITIONEN
    function applyFiltersDefinitions() {
        const term = document.getElementById('searchDefinitions').value.toLowerCase();
        
        const filters = {
            interval: document.getElementById('filterIntervalDef'),
            area: document.getElementById('filterAreaDef'),
            plant: document.getElementById('filterPlantDef')
        };
    
        const intervalVal = filters.interval ? filters.interval.value : 'all';
        const areaVal = filters.area ? filters.area.value : 'all';
        const plantVal = filters.plant ? filters.plant.value : 'all';
    
        // 1. Chips färben
        Object.values(filters).forEach(selectEl => {
            if (!selectEl) return;
            if (selectEl.value !== 'all') {
                selectEl.classList.remove('bg-white', 'text-slate-600', 'border-slate-300');
                selectEl.classList.add('bg-blue-50', 'text-blue-700', 'border-blue-200', 'font-bold');
            } else {
                selectEl.classList.add('bg-white', 'text-slate-600', 'border-slate-300');
                selectEl.classList.remove('bg-blue-50', 'text-blue-700', 'border-blue-200', 'font-bold');
            }
        });
        updateFilterBadge('filterBadgeDef', ['filterIntervalDef', 'filterAreaDef', 'filterPlantDef'], 'filterResetDef');
    
        const groupIds = ['bodyDefWeekly', 'bodyDefMonthly', 'bodyDefYearly', 'bodyDefOther'];
    
        groupIds.forEach(bodyId => {
            const bodyEl = document.getElementById(bodyId);
            if (!bodyEl) return;
    
            const rows = bodyEl.querySelectorAll('tbody tr');
            let hasVisibleItems = false;
    
            rows.forEach(row => {
                const id = row.cells[0].textContent.trim();
                const def = taskDefinitions.find(d => d.id === id);
                
                if(!def) return;
    
                let matches = true;
    
                // 1. Text
                if (term && !row.textContent.toLowerCase().includes(term)) matches = false;
                
                // 2. Intervall
                if (matches && intervalVal !== 'all' && def.intervalUnit !== intervalVal) matches = false;
                
                // 3. Bereich
                if (matches && areaVal !== 'all' && def.topic !== areaVal) matches = false;
    
                // 4. Anlage
                if (matches && plantVal !== 'all') {
                    const pIndex = def.plantIndex || '';
                    if (!pIndex.startsWith(plantVal + '.') && !pIndex.startsWith(plantVal + ' ')) matches = false;
                }
    
                if (matches) {
                    row.classList.remove('hidden');
                    hasVisibleItems = true;
                } else {
                    row.classList.add('hidden');
                }
            });
    
            // Kachel-Verhalten
            const hasActiveFilter = term || intervalVal!=='all' || areaVal!=='all' || plantVal!=='all';
    
            if (hasActiveFilter && hasVisibleItems) {
                bodyEl.classList.remove('hidden');
                const btn = bodyEl.parentElement.querySelector('button');
                const icon = btn.querySelector('.material-icons-outlined:last-child');
                if (icon) icon.classList.remove('rotate-180');
            } else if (hasActiveFilter && !hasVisibleItems) {
                bodyEl.classList.add('hidden');
                const btn = bodyEl.parentElement.querySelector('button');
                const icon = btn.querySelector('.material-icons-outlined:last-child');
                if (icon) icon.classList.add('rotate-180');
            }
        });
    }

    function canUserReview(order) {
        // 1. Admin darf immer
        if (currentUserIsAdmin) return true;

        // 2. Proposed Actions: direkt per Rolle prüfen (neue Einträge sind noch nicht in serverPermissions)
        if (order.type === 'A' && order.status === 'Proposed') {
            return currentUserRoles.includes('TESTENGINEER') || currentUserRoles.includes('MAINTENANCEMANAGER');
        }

        // 3. Server-Entscheidung nutzen
        return serverPermissions[order.id] === true;
    }
    function switchAnalysisView(viewName, btn) {
        // 1. Buttons umschalten
        document.querySelectorAll('.analyse-toggle-btn').forEach(b => b.classList.remove('active'));
        if(btn) btn.classList.add('active');
        
        // 2. Container umschalten
        document.querySelectorAll('.analysis-subview').forEach(el => el.classList.add('hidden'));
        document.getElementById('view-' + viewName).classList.remove('hidden');
        
        // 3. WICHTIG: Charts neu rendern/resizen, da sie sonst in versteckten Containern oft kaputt gehen
        // Wir rufen einfach die Haupt-Render-Funktion auf, die ist idempotent genug.
        renderMaintenanceAnalysis();
    }
    function openAssetHistory(assetName) {
        const stats = globalAssetStats.find(a => a.name === assetName);
        if(!stats) return;
    
        document.getElementById('assetHistoryTitle').textContent = assetName;
        document.getElementById('assetHistorySubtitle').textContent = `${stats.total} Issues gesamt | ${stats.critical} Kritisch`;
        
        const container = document.getElementById('assetHistoryTimeline');
        container.innerHTML = '';
    
        // Zugehörige Orders finden und sortieren (Neu -> Alt)
        const historyOrders = maintenanceOrders.filter(o => stats.history.includes(o.id));
        historyOrders.sort((a,b) => new Date(b.date) - new Date(a.date));
    
        historyOrders.forEach(o => {
            const dateObj = new Date(o.date);
            const dateStr = dateObj.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' });
            
            // Farben nach Prio
            let dotColor = "bg-blue-500 ring-blue-200";
            let icon = "info";
            if(o.priority === 'Critical' || o.isBlocker) { dotColor = "bg-red-500 ring-red-200"; icon = "report_problem"; }
            else if(o.priority === 'High') { dotColor = "bg-orange-500 ring-orange-200"; icon = "warning"; }
            else if(o.status === 'Completed') { dotColor = "bg-green-500 ring-green-200"; icon = "check_circle"; }
    
            const item = document.createElement('div');
            item.className = "ml-6 relative";
            item.innerHTML = `
                <span class="absolute -left-[31px] top-1 flex h-6 w-6 items-center justify-center rounded-full ring-4 ring-white ${dotColor} text-white">
                    <span class="material-icons-outlined text-[14px]">${icon}</span>
                </span>
                <div class="bg-white p-4 rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer" onclick="openWorkOrderModal('${o.id}')">
                    <div class="flex justify-between items-start mb-1">
                        <span class="text-xs font-bold text-slate-400 uppercase tracking-wider">${dateStr}</span>
                        <span class="text-[10px] bg-slate-100 text-slate-600 px-2 py-0.5 rounded-full border border-slate-200">${o.status}</span>
                    </div>
                    <h4 class="font-bold text-slate-800 text-sm mb-1">${o.task}</h4>
                    <p class="text-xs text-slate-500 line-clamp-2">${o.description || 'Keine Beschreibung'}</p>
                    <div class="mt-2 flex items-center gap-2 text-[10px] text-slate-400 border-t border-slate-100 pt-2">
                        <span>ID: ${o.id}</span>
                        <span>•</span>
                        <span>Von: ${o.logs?.[0]?.user || 'System'}</span>
                    </div>
                </div>
            `;
            container.appendChild(item);
        });
    
        // Modal öffnen
        const modal = document.getElementById('assetHistoryModal');
        modal.classList.remove('hidden');
        // document.body.classList.add('modal-active'); // Optional, wenn Body-Lock gewünscht
    }
    
    function closeAssetHistoryModal() {
        document.getElementById('assetHistoryModal').classList.add('hidden');
    }
    
    function renderChart(id, type, data, options = {}) {
        const ctx = document.getElementById(id);
        if (analysisCharts[id]) analysisCharts[id].destroy(); // Alten Chart löschen
        
        analysisCharts[id] = new Chart(ctx, {
            type: type,
            data: data,
            options: {
                responsive: true,
                ...options
            }
        });
    }
    
    // --- LIGHTBOX FUNKTIONEN ---
    function openLightbox(url, caption = '') {
        const modal = document.getElementById('lightboxModal');
        const img = document.getElementById('lightboxImage');
        const cap = document.getElementById('lightboxCaption');
        
        if (modal && img) {
            img.src = url;
            if(cap) cap.textContent = caption;
            modal.classList.remove('hidden');
            document.body.classList.add('modal-active'); // Scrollen verhindern
        }
    }
    
    function closeLightbox() {
        const modal = document.getElementById('lightboxModal');
        if (modal) {
            modal.classList.add('hidden');
            document.getElementById('lightboxImage').src = '';
            // Nur entfernen, wenn kein anderes Modal offen ist (einfacher Check)
            if (!document.querySelector('.modal:not(.hidden):not(#lightboxModal)')) {
                 document.body.classList.remove('modal-active');
            }
        }
    }
    
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape' && !document.getElementById('lightboxModal').classList.contains('hidden')) {
            closeLightbox();
        }
    });   
     
    function setupAssetTagInput(order, initialParsedAssets = []) {
        currentAssetTags = initialParsedAssets.map(asset => asset.label); 

        if (order.sourceTask) {
            const parentOrder = maintenanceOrders.find(p => p.id === order.sourceTask);
            availableAssetOptions = parentOrder ? (parentOrder.maintenanceObjects || []) : [];
            availableAssetOptions = availableAssetOptions.map(assetStr => {
                try { if(assetStr.startsWith('{')) return JSON.parse(assetStr).label; } catch(e) {}
                return assetStr;
            });
        } else {
            availableAssetOptions = allHardwareAssets;
        }
        
        renderAssetTags(); // Rendert die Tags basierend auf den `currentAssetTags`
    
        const input = document.getElementById('woEditAssetInput');
        const dropdown = document.getElementById('woEditAssetDropdown');
        const newInput = input.cloneNode(true);
        input.parentNode.replaceChild(newInput, input);
    
        newInput.addEventListener('input', () => {
            const val = newInput.value.toLowerCase();
            dropdown.innerHTML = '';
            if (!val) { dropdown.classList.add('hidden'); return; }
    
            const filtered = availableAssetOptions.filter(
                asset => asset.toLowerCase().includes(val) && !currentAssetTags.includes(asset)
            ).slice(0, 10);
    
            if (filtered.length > 0) {
                filtered.forEach(asset => {
                    const div = document.createElement('div');
                    div.className = 'tag-dropdown-item';
                    div.textContent = asset;
                    div.onclick = () => addAssetTag(asset);
                    dropdown.appendChild(div);
                });
                dropdown.classList.remove('hidden');
            } else {
                dropdown.classList.add('hidden');
            }
        });
        
        newInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                addAssetTag(newInput.value.trim());
            }
        });
    }
    
    function renderAssetTags() {
        const container = document.getElementById('woEditAssetTags');
        const input = document.getElementById('woEditAssetInput');
        Array.from(container.children).forEach(c => { if(c.classList.contains('tag-chip')) c.remove(); });
        currentAssetTags.forEach(tag => {
            const chip = document.createElement('span');
            chip.className = 'tag-chip';
            chip.innerHTML = `${tag}<span class="tag-chip-remove" onclick="removeAssetTag('${tag}')">×</span>`;
            container.insertBefore(chip, input);
        });
        input.value = '';
    }
    
    function addAssetTag(tag) {
        if (tag && !currentAssetTags.includes(tag)) {
            currentAssetTags.push(tag);
            renderAssetTags();
            
            // NEU: Liste unten aktualisieren (falls wir im Edit Mode eines Auftrags sind)
            const order = maintenanceOrders.find(o => o.id === currentOrderId);
            if (order && isWorkOrderInEditMode) {
                 renderEditBlockerList(order, currentAssetTags);
            }
        }
        document.getElementById('woEditAssetDropdown').classList.add('hidden');
        document.getElementById('woEditAssetInput').focus();
    }
    
    window.removeAssetTag = function(tag) {
        currentAssetTags = currentAssetTags.filter(t => t !== tag);
        renderAssetTags();
        
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (order && isWorkOrderInEditMode) {
             renderEditBlockerList(order, currentAssetTags);
        }
    }
    function switchToWoEditMode() {
        isWorkOrderInEditMode = true;
        
        // Aktuelles Order-Objekt laden
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;
    
        // Berechtigung prüfen: Admin darf immer, andere nur wenn nicht abgeschlossen
        const canEdit = order.status !== 'Completed' || currentUserIsAdmin;
    
        // --- 1. HEADER BEREICH (Titel & Start-Datum) ---
        
        // Titel Input zeigen
        document.getElementById('woTitle').classList.add('hidden');
        document.getElementById('woEditTitleContainer').classList.remove('hidden');
        const titleInput = document.getElementById('woEditTitle');
        titleInput.readOnly = !canEdit;
        if(!canEdit) titleInput.classList.add('bg-slate-100', 'text-slate-500');
    
        // Datums-Anzeigen ausblenden
        const dateDisplay = document.getElementById('woDateDisplay');
        if(dateDisplay) dateDisplay.classList.add('hidden');
        
        const compDisplay = document.getElementById('woCompletionDisplay');
        if(compDisplay) compDisplay.classList.add('hidden');
    
        // Start-Datum Input (Header) einblenden & entsperren
        const dateContainer = document.getElementById('woEditDateContainer');
        dateContainer.classList.remove('hidden');
        
        const startDateInput = document.getElementById('woEditDate');
        const startShiftInput = document.getElementById('woEditStartShift');
        
        startDateInput.disabled = !canEdit;
        startShiftInput.disabled = !canEdit;
        
        if (canEdit) {
            startDateInput.classList.remove('bg-slate-100', 'text-slate-500');
            startShiftInput.classList.remove('bg-slate-100', 'text-slate-500');
        }
    
        // --- 2. DETAILS BEREICH (Beschreibung & Metadaten) ---
        
        document.getElementById('woDescriptionViewContainer').classList.add('hidden');
        document.getElementById('woEditDetailsContainer').classList.remove('hidden');
        
        const descInput = document.getElementById('woEditDescription');
        descInput.readOnly = !canEdit;
        
        // Location (Ort)
        document.getElementById('woLocationView').classList.add('hidden');
        document.getElementById('woLocationEdit').classList.remove('hidden');
        const plantInput = document.getElementById('woEditPlantIndex');
        plantInput.readOnly = !canEdit;
    
        // Flexible Felder (Prio, Kat, Assets) - Nur für Issues/Actions
        const isFlexible = order.type === 'I' || order.type === 'A';
        const metaContainer = document.getElementById('woEditMetaFields');
        const assetContainer = document.getElementById('woEditAssetContainer');
        
        if (metaContainer) metaContainer.style.display = isFlexible ? 'grid' : 'none';
        if (assetContainer) assetContainer.classList.toggle('hidden', !isFlexible);
    
        if (isFlexible) {
            document.getElementById('woEditPriority').disabled = !canEdit;
            document.getElementById('woEditCategory').disabled = !canEdit;
            
            // Asset Tags initialisieren
            // HINWEIS: Wir rufen hier setupAssetTagInput auf, um sicherzustellen, 
            // dass die Tags für diesen Edit-Vorgang frisch sind.
            const parsedAssets = (order.affectedAssets || []).map(assetStr => {
                try { if(assetStr.trim().startsWith('{')) return JSON.parse(assetStr); } catch(e){}
                const item = hardwareInventoryList.find(h => h.label === assetStr);
                return { id: item ? item.id : null, label: assetStr };
            });
            setupAssetTagInput(order, parsedAssets);
            
            const assetInput = document.getElementById('woEditAssetInput');
            if(assetInput) assetInput.disabled = !canEdit;
        }
    
        // --- 3. BLOCKER SETTINGS (Nur für Issues) ---
        
        const blockerSettings = document.getElementById('woEditBlockerSettings');
        if (blockerSettings) {
            if (order.type === 'I') {
                blockerSettings.classList.remove('hidden');
                
                // Unit Blocker Checkbox
                const unitCb = document.getElementById('woEditUnitBlocker');
                const isUnit = order.isUnitBlocker === true || order.isUnitBlocker === "true";
                unitCb.checked = isUnit;
                unitCb.disabled = !canEdit;
                
                // Hardware Liste rendern
                renderEditBlockerList(order, currentAssetTags);
                
                // Falls nicht editierbar, Checkboxen in der Liste sperren
                if (!canEdit) {
                    setTimeout(() => {
                        document.querySelectorAll('.edit-blocker-cb').forEach(cb => cb.disabled = true);
                    }, 50);
                }
            } else {
                blockerSettings.classList.add('hidden');
            }
        }
    
        // --- 4. ABSCHLUSS DATEN (Nur wenn Completed) ---
        
        const compEditContainer = document.getElementById('woEditCompletionContainer');
        if (compEditContainer) {
            if (order.status === 'Completed') {
                compEditContainer.classList.remove('hidden');
                // Abschlussdaten sollten für Admins/Manager immer editierbar sein, 
                // auch wenn der Auftrag "completed" ist (dafür ist das Feld ja da).
                // Wir nutzen hier 'canEdit' (was true ist, wenn Admin, oder false wenn User).
                // Falls User seine eigenen Fehler korrigieren soll, müsste man canEdit anpassen.
                // Aktuell: Nur Admins können abgeschlossene ändern.
                
                document.getElementById('woEditCompletedDate').disabled = !canEdit;
                document.getElementById('woEditCompletedShift').disabled = !canEdit;
            } else {
                compEditContainer.classList.add('hidden');
            }
        }
    
        // --- 5. BUTTONS UMSCHALTEN ---
        
        document.getElementById('statusActions').classList.add('hidden');
        document.getElementById('woEditButtons').classList.remove('hidden');
        
        const btnEdit = document.getElementById('btnEditOrder');
        if(btnEdit) btnEdit.classList.add('hidden');
        
        document.getElementById('woQuickActions').classList.add('hidden');
        
        // Visuelles Feedback für Inputs (Optional)
        const allEditInputs = document.querySelectorAll('#workOrderModal input:not([type="hidden"]), #workOrderModal textarea, #workOrderModal select');
        allEditInputs.forEach(el => {
            if (!el.readOnly && !el.disabled) {
                el.classList.add('ring-2', 'ring-blue-100', 'border-blue-400', 'bg-blue-50/20');
                el.classList.remove('border-slate-300');
            }
        });
    }
    
    function switchToWoViewMode() {
        isWorkOrderInEditMode = false;
        
        // Header Titel
        document.getElementById('woTitle').classList.remove('hidden');
        document.getElementById('woEditTitleContainer').classList.add('hidden');
        
        const dateDisplay = document.getElementById('woDateDisplay');
        if (dateDisplay) dateDisplay.classList.remove('hidden');
        
        // Completion Display wieder einblenden (falls Inhalt da ist)
        const compDisplay = document.getElementById('woCompletionDisplay');
        if (compDisplay && compDisplay.innerHTML !== '') {
            compDisplay.classList.remove('hidden');
        }
        
        // Edit-Container für Zeit ausblenden
        document.getElementById('woEditDateContainer').classList.add('hidden');
        // ------------------------------------------
        
        document.getElementById('woDescriptionViewContainer').classList.remove('hidden');
        document.getElementById('woEditDetailsContainer').classList.add('hidden');
        
        // Falls vorhanden (Blocker-Settings)
        const blockerSettings = document.getElementById('woEditBlockerSettings');
        if (blockerSettings) blockerSettings.classList.add('hidden');
        
        document.getElementById('woLocationView').classList.remove('hidden');
        document.getElementById('woLocationEdit').classList.add('hidden');
    
        document.getElementById('statusActions').classList.remove('hidden');
        document.getElementById('woEditButtons').classList.add('hidden');
        
        const btnEdit = document.getElementById('btnEditOrder');
        if (btnEdit) btnEdit.classList.remove('hidden');
        
        document.getElementById('woQuickActions').classList.remove('hidden');
        
        // Reset Styles der Inputs
        const allInputs = document.querySelectorAll('#workOrderModal input, #workOrderModal textarea, #workOrderModal select');
        allInputs.forEach(el => {
            el.classList.remove('ring-2', 'ring-blue-100', 'border-blue-400', 'bg-blue-50/20');
            el.classList.add('border-slate-300');
        });
        
        // Abschluss-Edit-Container ausblenden
        const compEditContainer = document.getElementById('woEditCompletionContainer');
        if (compEditContainer) compEditContainer.classList.add('hidden');
    }

    async function saveWorkOrderChanges() {
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;
        
        let changes = [];
        
        // 1. ALLE Werte auslesen (Fix für ReferenceError)
        const newDate = document.getElementById('woEditDate').value;
        const newStartShift = document.getElementById('woEditStartShift').value;
        const newDesc = document.getElementById('woEditDescription').value.trim(); // Hier war der Fehler!
        
        // 2. Vergleiche Startdaten
        if (order.date !== newDate || (order.startShift || 'AM') !== newStartShift) {
            changes.push(`Startzeit geändert auf ${newDate} ${newStartShift}.`);
            order.date = newDate;
            order.startShift = newStartShift;
        }
        
        // 3. Vergleiche Beschreibung
        if ((order.description || '') !== newDesc) {
            changes.push("Beschreibung aktualisiert.");
            order.description = newDesc;
        }
    
        // 4. Abschlussdaten (Nur wenn Completed)
        if (order.status === 'Completed') {
            const newCompDate = document.getElementById('woEditCompletedDate').value;
            const newCompShift = document.getElementById('woEditCompletedShift').value;
            
            // Prüfung auf Änderung
            if (order.completedDate !== newCompDate || (order.completedShift || 'PM') !== newCompShift) {
                changes.push(`Abschlusszeit korrigiert auf ${newCompDate} ${newCompShift}.`);
                order.completedDate = newCompDate;
                order.completedShift = newCompShift;
            }
        }
    
        // 5. Flexible Tasks (Issues/Actions) Metadaten
        if (order.type === 'I' || order.type === 'A') {
            const newTitle = document.getElementById('woEditTitle').value.trim();
            const newPrio = document.getElementById('woEditPriority').value;
            const newCat = document.getElementById('woEditCategory').value;
            
            if (order.task !== newTitle) {
                changes.push("Titel geändert.");
                order.task = newTitle;
            }
            if (order.priority !== newPrio) {
                changes.push(`Priorität auf "${newPrio}" geändert.`);
                order.priority = newPrio;
            }
            const oldCat = order.topic || order.category;
            if (oldCat !== newCat) {
                changes.push(`Bereich geändert.`);
                order.topic = newCat;
                order.category = newCat;
            }
        }
    
        // 6. Blocker Logik (Issues)
        if (order.type === 'I') {
            const newUnitBlocker = document.getElementById('woEditUnitBlocker').checked;
            if (order.isUnitBlocker !== newUnitBlocker) {
                changes.push(`Anlagen-Sperre ${newUnitBlocker ? 'aktiviert' : 'aufgehoben'}.`);
                order.isUnitBlocker = newUnitBlocker;
            }
    
            // Hardware Blocker — Feld normalisieren bevor es verwendet wird
            order.blockedAssetIds = order.blockedAssetIds || [];
            const oldBlockedIds = new Set(order.blockedAssetIds);
            const newBlockedIds = new Set(
                Array.from(document.querySelectorAll('.edit-blocker-cb:checked')).map(cb => cb.value)
            );
            
            const idsToBlock = [...newBlockedIds].filter(x => !oldBlockedIds.has(x));
            const idsToFree = [...oldBlockedIds].filter(x => !newBlockedIds.has(x));
            
            if (idsToBlock.length > 0 || idsToFree.length > 0) {
                changes.push(`Hardware-Sperren aktualisiert (+${idsToBlock.length} / -${idsToFree.length}).`);
                order.blockedAssetIds = [...newBlockedIds];
                
                // API Calls
                if (idsToBlock.length > 0) {
                    await fetch('/api/hardware/set_status', {
                        method: 'POST', headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({ ids: idsToBlock, status: 'Maintenance' })
                    });
                }
                if (idsToFree.length > 0) {
                    await fetch('/api/hardware/set_status', {
                        method: 'POST', headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({ ids: idsToFree, status: 'Available' })
                    });
                }
            }
            order.isBlocker = order.isUnitBlocker || order.blockedAssetIds.length > 0;
        }
    
        // 7. Ort (Facility Unit)
        const newPlantIndex = document.getElementById('woEditPlantIndex').value.trim();
        let newFacilityUnitId = document.getElementById('woEditFacilityUnitId').value;
        if (!newFacilityUnitId && facilityUnitMap[newPlantIndex]) {
            newFacilityUnitId = facilityUnitMap[newPlantIndex];
        }
    
        if (order.plantIndex !== newPlantIndex || order.facilityUnitId !== newFacilityUnitId) {
            changes.push(`Ort geändert.`);
            order.plantIndex = newPlantIndex;
            order.facilityUnitId = newFacilityUnitId; 
        }
      
        // 8. Speichern & Refresh
        if (changes.length > 0) {
            order.logs = order.logs || [];
            order.logs.push({
                user: currentUser,
                timestamp: new Date().toLocaleString(),
                note: `Auftrag bearbeitet: ${changes.join(' ')}`
            });
            await saveOrder(order);
            renderOrdersTable();
        }
        
        switchToWoViewMode();
        openWorkOrderModal(currentOrderId);
    }
    // --- SUCHFUNKTIONEN ---
    

    async function saveAllMaintenanceData() {
        try {
            await fetch('/api/maintenance/save_all', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    orders: maintenanceOrders,
                    definitions: taskDefinitions
                })
            });
        } catch (e) {
            console.error("Fehler beim Speichern der kombinierten Daten:", e);
            alert("Fehler beim Speichern der Daten. Bitte Konsole prüfen.");
        }
    }    

    function openModal(modalId) {
        const modal = document.getElementById(modalId);
        if (modal) {
            modal.classList.remove('hidden');
            // Optional: Body-Scroll sperren
            document.body.classList.add('modal-active');
        }
    }

    function closeModal(modalId) {
        const modal = document.getElementById(modalId);
        if (modal) {
            modal.classList.add('hidden');
            // Prüfen, ob noch andere Modale offen sind, bevor der Body-Scroll freigegeben wird
            const anyModalOpen = document.querySelector('.modal:not(.hidden)');
            if (!anyModalOpen) {
                document.body.classList.remove('modal-active');
            }
        }
    }
    async function saveOrder(order) { try { const r = await fetch('/api/maintenance/orders', { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(order) }); if (!r.ok) console.error("Fehler beim Speichern des Auftrags:", await r.text()); } catch (e) { console.error("Fehler beim Speichern des Auftrags:", e); } }
    async function deleteOrder(orderId) { try { const r = await fetch(`/api/maintenance/orders/${orderId}`, { method: 'DELETE' }); if (!r.ok) console.error("Fehler beim Löschen des Auftrags:", await r.text()); } catch (e) { console.error("Fehler beim Löschen des Auftrags:", e); } }
    async function deleteOrders(ids) { try { const r = await fetch('/api/maintenance/orders/bulk-delete', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ids}) }); if (!r.ok) console.error("Fehler beim Löschen der Aufträge:", await r.text()); } catch (e) { console.error("Fehler beim Löschen der Aufträge:", e); } }
    async function saveOrdersBatch(orders) { try { const r = await fetch('/api/maintenance/orders/batch', { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({orders}) }); if (!r.ok) console.error("Fehler beim Batch-Speichern:", await r.text()); } catch (e) { console.error("Fehler beim Batch-Speichern:", e); } }
    async function saveDefinitionItem(def) { try { await fetch(`/api/maintenance/definitions/${def.id}`, { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(def) }); } catch (e) { console.error("Fehler beim Speichern der Definition:", e); } }
    async function deleteDefinitionItem(id) { try { await fetch(`/api/maintenance/definitions/${id}`, { method: 'DELETE' }); } catch (e) { console.error("Fehler beim Löschen der Definition:", e); } }
    async function saveCompaniesData() { try { await fetch('/api/maintenance/save_companies', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({companies: companies}) }); } catch (e) { console.error("Fehler beim Speichern der Firmen:", e); } }

    function openAdHocContextModal() {
        document.getElementById('contextSearchInput').value = '';
        document.getElementById('contextSearchResults').innerHTML = '<p class="p-8 text-center text-slate-400 text-sm">Bitte geben Sie einen Suchbegriff ein.</p>';
        openModal('adHocContextModal');
    }

    function closeAdHocContextModal() {
        closeModal('adHocContextModal');
    }

    function selectAdHocContext(assetItem) {
        let valueToStore = assetItem;
        if (typeof assetItem === 'string') {
             valueToStore = { id: null, label: assetItem };
        }

        adHocContext = {
            type: 'asset',
            value: valueToStore // Speichert {id: "...", label: "..."}
        };
        closeAdHocContextModal();
        createLinkedIssue(); 
    }
    
    function createUnlinkedIssue() {
        adHocContext = {
            type: 'unlinked'
        };
        closeAdHocContextModal();
        createLinkedIssue();
    }

    document.getElementById('contextSearchInput').addEventListener('input', (e) => {
        const searchTerm = e.target.value.toLowerCase();
        const resultsContainer = document.getElementById('contextSearchResults');
        
        if (searchTerm.length < 2) {
            resultsContainer.innerHTML = '<p class="p-8 text-center text-slate-400 text-sm">Bitte geben Sie einen Suchbegriff ein.</p>';
            return;
        }

        // NEU: Filtern auf Objekt-Basis (item.label)
        const filteredAssets = hardwareInventoryList.filter(item => {
            // item ist jetzt ein Objekt {id, label, ...}
            // Fallback für alte Strings, falls noch welche da sind
            const text = item.label || item; 
            return text.toLowerCase().includes(searchTerm);
        });

        if (filteredAssets.length === 0) {
            resultsContainer.innerHTML = '<p class="p-4 text-center text-slate-500">Kein passendes Asset gefunden.</p>';
            return;
        }

        resultsContainer.innerHTML = '';
        
        filteredAssets.forEach(item => {
            const div = document.createElement('div');
            div.className = 'px-4 py-3 border-b border-slate-100 last:border-0 hover:bg-blue-50 cursor-pointer text-sm';
            
            // Text für die Anzeige
            const displayText = item.label || item;
            
            div.textContent = displayText;
            
            // Beim Klick übergeben wir jetzt das Objekt (oder den String, wenn alt)
            // Wir nutzen JSON.stringify, um das Objekt sicher als Argument zu übergeben
            // ABER: Da wir das Objekt global haben, reicht es, den Identifier zu übergeben.
            // Einfacher: Wir bauen eine Wrapper-Funktion im onclick, die den Scope nutzt.
            div.onclick = () => selectAdHocContext(item);
            
            resultsContainer.appendChild(div);
        });
    });
    let companiesSortState = { col: 'name', dir: 'asc' };

    function sortCompanies(col) {
        if (companiesSortState.col === col) {
            companiesSortState.dir = companiesSortState.dir === 'asc' ? 'desc' : 'asc';
        } else {
            companiesSortState.col = col;
            companiesSortState.dir = 'asc';
        }
        renderCompaniesTable();
    }

    function renderCompaniesTable() { const tbody = document.getElementById('companiesBody'); tbody.innerHTML = ''; if(companies.length === 0) { tbody.innerHTML = '<tr><td colspan="4" class="px-6 py-8 text-center text-slate-400 italic">Keine Firmen angelegt.</td></tr>'; return; }
        // Sort icons aktualisieren
        ['name','contact','services'].forEach(c => {
            const el = document.getElementById('sort-icon-' + c);
            if (!el) return;
            if (companiesSortState.col === c) {
                el.textContent = companiesSortState.dir === 'asc' ? 'arrow_upward' : 'arrow_downward';
                el.classList.remove('text-slate-400'); el.classList.add('text-blue-500');
            } else {
                el.textContent = 'unfold_more';
                el.classList.remove('text-blue-500'); el.classList.add('text-slate-400');
            }
        });
        // Sortierung anwenden
        const sorted = [...companies].sort((a, b) => {
            let va = '', vb = '';
            if (companiesSortState.col === 'name') { va = (a.name || '').toLowerCase(); vb = (b.name || '').toLowerCase(); }
            else if (companiesSortState.col === 'contact') { va = (a.contacts && a.contacts[0] ? a.contacts[0].name : a.contact_person || '').toLowerCase(); vb = (b.contacts && b.contacts[0] ? b.contacts[0].name : b.contact_person || '').toLowerCase(); }
            else if (companiesSortState.col === 'services') { va = (a.services || '').toLowerCase(); vb = (b.services || '').toLowerCase(); }
            return companiesSortState.dir === 'asc' ? va.localeCompare(vb) : vb.localeCompare(va);
        });
        sorted.forEach(comp => { const contacts = comp.contacts && comp.contacts.length ? comp.contacts : (comp.contact_person ? [{name: comp.contact_person, email: comp.email||'', phone: comp.phone||''}] : []); const contactsHtml = contacts.length ? contacts.map(c => `<div class="mb-1 last:mb-0"><div class="font-medium text-slate-700">${c.name||''}</div>${c.email?`<div class="text-slate-400"><span class="material-icons-outlined text-[10px] align-middle mr-0.5">mail</span>${c.email}</div>`:''}${c.phone?`<div class="text-slate-400"><span class="material-icons-outlined text-[10px] align-middle mr-0.5">phone</span>${c.phone}</div>`:''}</div>`).join('') : '<span class="text-slate-400">-</span>'; const tr = document.createElement('tr'); tr.className = "hover:bg-slate-50 border-b border-slate-100"; tr.innerHTML = `<td class="px-6 py-3 font-medium text-slate-800">${comp.name}</td><td class="px-6 py-3 text-xs">${contactsHtml}</td><td class="px-6 py-3 text-slate-500 text-xs truncate max-w-xs" title="${comp.services || ''}">${comp.services || '-'}</td><td class="px-6 py-3 text-right space-x-1"><button class="text-slate-400 hover:text-blue-600 p-1 transition-colors" onclick="openCompanyModal('${comp.id}')"><span class="material-icons-outlined text-lg">edit</span></button><button class="text-slate-400 hover:text-red-600 p-1 transition-colors" onclick="deleteCompany('${comp.id}')"><span class="material-icons-outlined text-lg">delete_outline</span></button></td></tr>`; tbody.appendChild(tr); }); updateCompanyDropdown(); }
    function updateCompanyDropdown() { const select = document.getElementById('defExternalCompany'); if(!select) return; const firstOpt = select.options[0]; select.innerHTML = ''; select.appendChild(firstOpt); companies.forEach(comp => { const opt = document.createElement('option'); opt.value = comp.id; opt.textContent = comp.name; select.appendChild(opt); }); }
    function addContactRow(name='', email='', phone='') { const list = document.getElementById('compContactsList'); const idx = list.children.length; const div = document.createElement('div'); div.className = 'flex gap-2 items-start bg-white border border-slate-200 rounded-md p-2 relative'; div.innerHTML = `<div class="flex-1 space-y-1"><input class="w-full border-slate-300 rounded text-sm comp-contact-name" placeholder="Name, z.B. Hr. Schmidt" value="${name}"><div class="grid grid-cols-2 gap-1"><input class="w-full border-slate-300 rounded text-sm comp-contact-email" type="email" placeholder="E-Mail" value="${email}"><input class="w-full border-slate-300 rounded text-sm comp-contact-phone" type="tel" placeholder="Telefon" value="${phone}"></div></div><button type="button" onclick="this.closest('div.flex').remove()" class="text-slate-300 hover:text-red-500 mt-1 flex-shrink-0"><span class="material-icons-outlined text-lg">remove_circle_outline</span></button>`; list.appendChild(div); }
    function openCompanyModal(id = null) { document.getElementById('companyForm').reset(); document.getElementById('compId').value = ''; document.getElementById('compContactsList').innerHTML = ''; document.getElementById('compModalTitle').textContent = 'Firma anlegen'; if(id) { const comp = companies.find(c => c.id === id); if(comp) { document.getElementById('compId').value = comp.id; document.getElementById('compName').value = comp.name; document.getElementById('compServices').value = comp.services || ''; document.getElementById('compModalTitle').textContent = 'Firma bearbeiten'; const contacts = comp.contacts && comp.contacts.length ? comp.contacts : (comp.contact_person ? [{name: comp.contact_person, email: comp.email||'', phone: comp.phone||''}] : []); contacts.forEach(c => addContactRow(c.name||'', c.email||'', c.phone||'')); } } if(document.getElementById('compContactsList').children.length === 0) addContactRow(); openModal('companyModal'); }
    function closeCompanyModal() { closeModal('companyModal'); }
    function saveCompany() { const id = document.getElementById('compId').value; const name = document.getElementById('compName').value.trim(); if(!name) { alert("Name ist erforderlich."); return; } const contacts = Array.from(document.getElementById('compContactsList').children).map(row => ({ name: row.querySelector('.comp-contact-name').value.trim(), email: row.querySelector('.comp-contact-email').value.trim(), phone: row.querySelector('.comp-contact-phone').value.trim() })).filter(c => c.name || c.email || c.phone); const data = { id: id || `COMP-${Date.now()}`, name: name, contacts: contacts, services: document.getElementById('compServices').value.trim() }; if(id) { const idx = companies.findIndex(c => c.id === id); if(idx > -1) companies[idx] = data; } else { companies.push(data); } saveCompaniesData(); renderCompaniesTable(); closeCompanyModal(); }
    function deleteCompany(id) { if(confirm("Firma wirklich löschen?")) { companies = companies.filter(c => c.id !== id); saveCompaniesData(); renderCompaniesTable(); } }

    async function loadTestBenchOptions() { const select = document.getElementById('defTestbench'); if (!select) return; try { const response = await fetch('/api/test_cells'); let cells = []; if (response.ok) cells = await response.json(); select.innerHTML = ''; const facilityOpt = document.createElement('option'); facilityOpt.value = "Facility"; facilityOpt.textContent = "Facility"; select.appendChild(facilityOpt); const sortedCells = cells.sort((a, b) => a.TESTCELL.localeCompare(b.TESTCELL)); sortedCells.forEach(cell => { if (cell.TESTCELL === "Facility") return; const opt = document.createElement('option'); opt.value = cell.TESTCELL; opt.textContent = cell.TESTCELL; select.appendChild(opt); }); } catch (error) { select.innerHTML = '<option>Facility</option>'; } }
    async function loadHardwareInventory() { 
        try { 
            const response = await fetch('/api/test_equipment/data'); 
            if (response.ok) {
                // API liefert jetzt [{id:..., label:..., sn:..., pn:...}]
                hardwareInventoryList = await response.json();
                populateHardwareDatalist(); 
            }
        } catch(e) { 
            hardwareInventoryList = []; 
        } 
    }    
    function populateHardwareDatalist() { 
            const dl = document.getElementById('hardwareList'); 
            dl.innerHTML = ''; 
            hardwareInventoryList.forEach(item => { 
                const opt = document.createElement('option'); 
                // Wir setzen das Label als Value, damit der User danach suchen kann
                opt.value = item.label; 
                // Wir speichern die ID in einem Dataset-Attribut (für späteren Lookup)
                opt.dataset.id = item.id;
                dl.appendChild(opt); 
            }); 
        }
    function switchTab(name, btn) { document.querySelectorAll('.tab-content').forEach(e => e.classList.add('hidden')); document.getElementById('tab-' + name).classList.remove('hidden'); document.querySelectorAll('.tab-btn').forEach(b => { b.classList.remove('text-primary', 'border-primary'); b.classList.add('text-slate-500', 'border-transparent'); }); btn.classList.remove('text-slate-500', 'border-transparent'); btn.classList.add('text-primary', 'border-primary'); if(name === 'active' || name === 'history') renderOrdersTable(); if(name === 'definitions') renderDefinitionsTable(); if(name === 'companies') renderCompaniesTable(); if(name === 'analysis') renderMaintenanceAnalysis(); if(name === 'logbook')renderLogbook() }
    function toggleGroup(bodyId, btn) { const body = document.getElementById(bodyId); const icon = btn.querySelector('.material-icons-outlined:last-child'); if (body.classList.contains('hidden')) { body.classList.remove('hidden'); icon.classList.remove('rotate-180'); } else { body.classList.add('hidden'); icon.classList.add('rotate-180'); } }
    function toggleTaskRows(taskId, btn) { const container = document.querySelector(`.child-container-${taskId}`); const icon = btn.querySelector('.material-icons-outlined'); if (container.classList.contains('hidden')) { container.classList.remove('hidden'); icon.classList.add('rotate-90'); } else { container.classList.add('hidden'); icon.classList.remove('rotate-90'); } }

function renderOrdersTable() {
        const historyTbody = document.getElementById('historyOrdersBody'); historyTbody.innerHTML = ''; let doneCount = 0;
        
        // --- 1. NEUE BUCKET STRUKTUR ---
        const buckets = { critical: [], overdue: [], issues: [], upcoming: [], outlook: [], future: [] };
        
        const today = new Date(); today.setHours(0,0,0,0);
        const date14 = new Date(today); date14.setDate(today.getDate() + 14);
        const date60 = new Date(today); date60.setDate(today.getDate() + 60);
        
        const taskMap = new Map(); 
        const childrenMap = new Map(); 
        const rootTasks = [];
        
        maintenanceOrders.forEach(t => taskMap.set(t.id, t));
        
        // --- 2. HIERARCHIE & HISTORY ---
        const completedOrders = maintenanceOrders.filter(t => t.status === 'Completed');
        completedOrders.sort((a, b) => {
            const da = new Date(a.completedDate || a.date || '1970-01-01');
            const db = new Date(b.completedDate || b.date || '1970-01-01');
            return db - da;
        });
        completedOrders.forEach(t => {
            doneCount++;
            const tr = document.createElement('tr'); tr.className = "hover:bg-slate-50 group border-b border-slate-100";
            tr.innerHTML = `<td class="px-6 py-3 font-mono text-xs text-slate-500">${t.id}</td><td class="px-6 py-3 text-sm font-medium text-slate-700">${t.task}</td><td class="px-6 py-3 text-xs text-slate-500">${t.completedDate || t.date}</td><td class="px-6 py-3 text-xs text-slate-500">${t.completedBy || '-'}</td><td class="px-6 py-3 text-right"><button class="text-primary hover:underline text-xs font-medium" onclick="openWorkOrderModal('${t.id}')">Protokoll</button></td>`;
            historyTbody.appendChild(tr);
        });
        maintenanceOrders.forEach(t => {
            if(t.status !== 'Completed') {
                const parentId = t.sourceTask || t.linkedIssue;
                if(parentId && taskMap.has(parentId) && taskMap.get(parentId).status !== 'Completed') { 
                    if(!childrenMap.has(parentId)) childrenMap.set(parentId, []); 
                    childrenMap.get(parentId).push(t); 
                } else { 
                    rootTasks.push(t); 
                }
            }
        });

        // Helper für Abhängigkeiten
        const hasOverdueChildren = (taskId) => {
            if (!childrenMap.has(taskId)) return false;
            return childrenMap.get(taskId).some(child => {
                const childDate = new Date(child.date);
                if ((child.type === 'A' || child.type === 'M') && childDate < today) return true;
                return hasOverdueChildren(child.id);
            });
        };

        const hasCriticalDescendant = (taskId) => {
            if (!childrenMap.has(taskId)) return false;
            return childrenMap.get(taskId).some(child => {
                if (child.type === 'I' && (
                    child.priority === 'Critical' ||
                    child.priority === 'OPEN'     ||
                    child.isBlocker               ||
                    child.isUnitBlocker
                )) return true;
                return hasCriticalDescendant(child.id);
            });
        };

        // --- 3. SORTIERUNG IN BUCKETS ---
        // Priorität: Kritisch/Unbewertet > Überfällig > Offene Mängel > Zeitbuckets
        rootTasks.forEach(t => {
            const d = new Date(t.date);

            // Schritt 1: Kritisch/Unbewertet (höchste Priorität, schlägt alles andere)
            const isCriticalSelf = t.type === 'I' && (
                t.priority === 'Critical' ||
                t.priority === 'OPEN'     ||
                t.isBlocker               ||
                t.isUnitBlocker
            );
            if (isCriticalSelf || hasCriticalDescendant(t.id)) {
                buckets.critical.push(t);
                return;
            }

            // Schritt 2: Überfällig (datumbasiert, inkl. überfälliger Kinder)
            if (t.type === 'I') {
                if (hasOverdueChildren(t.id)) buckets.overdue.push(t);
                else buckets.issues.push(t);
            } else {
                if (d < today || hasOverdueChildren(t.id)) buckets.overdue.push(t);
                else if (d <= date14) buckets.upcoming.push(t);
                else if (d <= date60) buckets.outlook.push(t);
                else buckets.future.push(t);
            }
        });

        const sorter = (a, b) => new Date(a.date) - new Date(b.date); 
        Object.values(buckets).forEach(b => b.sort(sorter));
        
        // 1. Alle offenen Aufträge (Wartungen, Issues, Maßnahmen), die nicht 'Completed' sind
        let totalOpenCount = maintenanceOrders.filter(t => t.status !== 'Completed').length;
        
        // 2. Alle offenen Issues (Typ 'I'), die nicht 'Completed' sind
        let totalIssueCount = maintenanceOrders.filter(t => t.type === 'I' && t.status !== 'Completed').length;
        
        const countChildrenTypes = (taskId) => { let counts = { issues: 0, actions: 0 }; if (childrenMap.has(taskId)) { childrenMap.get(taskId).forEach(child => { if (child.type === 'I') counts.issues++; if (child.type === 'A') counts.actions++; const subCounts = countChildrenTypes(child.id); counts.issues += subCounts.issues; counts.actions += subCounts.actions; }); } return counts; };
        
        // --- 4. RENDER FUNKTION ---
        const renderBucket = (bucketData, containerId, bodyId, countId) => {
            const container = document.getElementById(containerId); 
            const body = document.getElementById(bodyId); 
            const countBadge = document.getElementById(countId);
            
            if (bucketData.length === 0) { container.classList.add('hidden'); return; }
            container.classList.remove('hidden'); 
            
            // --- NEUE REKURSIVE ZÄHLUNG (Mit Endlosschleifen-Schutz) ---
            const countAllInTree = (tasks, visited = new Set()) => {
                let count = 0;
                tasks.forEach(t => {
                    // Verhindere Zirkelbezüge!
                    if (visited.has(t.id)) return;
                    visited.add(t.id);
                    
                    count++; 
                    if (childrenMap.has(t.id)) {
                        count += countAllInTree(childrenMap.get(t.id), visited); 
                    }
                });
                return count;
            };
            countBadge.textContent = countAllInTree(bucketData);
            // -----------------------------------------------------------
            
            let listHtml = `<div class="flex flex-col bg-white divide-y divide-slate-100">`;
            
            const renderRowsRec = (tasks, level) => {
                let html = '';
                tasks.forEach(task => {
                    const hasChildren = childrenMap.has(task.id); 
                    const childCounts = countChildrenTypes(task.id);
                    
                    let indicatorsHtml = '';
                    // Blocker Badges
                    if (task.isUnitBlocker) {
                        indicatorsHtml += `<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-bold bg-red-600 text-white border border-red-700 uppercase tracking-wider" title="Betriebseinheit gesperrt">ANLAGE GESPERRT</span>`;
                    }
                    if (task.blockedAssetIds && task.blockedAssetIds.length > 0) {
                        indicatorsHtml += `<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-bold bg-orange-100 text-orange-800 border border-orange-200 uppercase tracking-wider" title="${task.blockedAssetIds.length} Assets gesperrt">HW SPERRE</span>`;
                    }
                    // Legacy Fallback (falls alte Daten noch nur 'isBlocker' haben)
                    if (task.isBlocker && !task.isUnitBlocker && (!task.blockedAssetIds || task.blockedAssetIds.length === 0)) {
                         indicatorsHtml += `<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-bold bg-red-100 text-red-700 border border-red-200 uppercase tracking-wider">BLOCKER</span>`;
                    }
                    if (childCounts.issues > 0) indicatorsHtml += `<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-yellow-100 text-yellow-700"><span class="material-icons-outlined text-[10px] mr-0.5">warning</span>${childCounts.issues}</span>`;
                    if (childCounts.actions > 0) indicatorsHtml += `<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-100 text-green-700"><span class="material-icons-outlined text-[10px] mr-0.5">build</span>${childCounts.actions}</span>`;
                    
                    let iconHtml = '';
                    const parseAssetLabel = (val) => {
                        try { if (val.trim().startsWith('{')) return JSON.parse(val).label || val; } catch(e) {}
                        return val;
                    };

                    if(task.type === 'I') { 
                        if (task.priority === 'OPEN') iconHtml = `<div class="w-5 h-5 rounded border border-slate-300 bg-slate-100 text-slate-500 flex items-center justify-center" title="Unbewertet"><span class="material-icons-outlined text-sm">question_mark</span></div>`;
                        else if (task.status === 'Review Ready') iconHtml = `<div class="w-5 h-5 rounded border border-purple-500 bg-purple-50 text-purple-600 flex items-center justify-center"><span class="material-icons-outlined text-sm">visibility</span></div>`; 
                        else if (task.status === 'In Progress') iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-yellow-500 bg-yellow-50 text-yellow-600 flex items-center justify-center"><span class="material-icons-outlined text-sm">play_arrow</span></div>`;
                        else iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-slate-300"></div>`; 
                    } else if (task.type === 'A') {
                        if (task.status === 'Proposed') iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-orange-400 bg-orange-50 text-orange-500 flex items-center justify-center" title="Wartet auf Freigabe"><span class="material-icons-outlined text-sm">hourglass_empty</span></div>`;
                        else if (task.status === 'In Progress') iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-green-500 bg-green-50 text-green-600 flex items-center justify-center"><span class="material-icons-outlined text-sm">play_arrow</span></div>`;
                        else iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-slate-300"></div>`;
                    } else { 
                        if(task.status === 'In Progress') iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-blue-500 bg-blue-50 text-blue-600 flex items-center justify-center"><span class="material-icons-outlined text-sm">play_arrow</span></div>`; 
                        else if(task.status === 'Review Ready') iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-purple-500 bg-purple-50 text-purple-600 flex items-center justify-center"><span class="material-icons-outlined text-sm">visibility</span></div>`; 
                        else iconHtml = `<div class="w-5 h-5 rounded-full border-2 border-slate-300"></div>`; 
                    }

                    let metaParts = [`<span class="font-mono font-semibold text-slate-500">${task.id}</span>`];
                    const displayArea = task.topic || task.category;
                    if (displayArea) { metaParts.push(`<span class="text-blue-600 font-medium">${displayArea}</span>`); }
                    let locationString = task.testbench || '';
                    if (task.plantIndex) {
                        if (locationString) locationString += ` (${task.plantIndex})`;
                        else locationString = task.plantIndex;
                    }
                    if (locationString) {
                        metaParts.push(locationString);
                    }                    
                    if ((task.type === 'I' || task.type === 'A') && task.affectedAssets && task.affectedAssets.length > 0) { 
                        const firstLabel = parseAssetLabel(task.affectedAssets[0]);
                        const count = task.affectedAssets.length; 
                        const label = count === 1 ? firstLabel : `${count} Assets`; 
                        const tooltip = task.affectedAssets.map(parseAssetLabel).join(', '); 
                        metaParts.push(`<span class="font-mono text-slate-600 bg-slate-100 px-1 rounded cursor-help" title="${tooltip}">${label}</span>`); 
                    }
                    else if ((task.type === 'I' || task.type === 'A') && task.affectedAsset) { 
                        metaParts.push(`<span class="font-mono text-slate-600 bg-slate-100 px-1 rounded" title="Betroffenes Asset">${parseAssetLabel(task.affectedAsset)}</span>`); 
                    } 
                    else if (task.maintenanceObjects && task.maintenanceObjects.length) { 
                        const firstLabel = parseAssetLabel(task.maintenanceObjects[0]);
                        const suffix = (task.maintenanceObjects.length > 1 ? ` +${task.maintenanceObjects.length-1}` : '');
                        metaParts.push(firstLabel + suffix); 
                    }
                    else if (task.maintenanceObject) { 
                        metaParts.push(parseAssetLabel(task.maintenanceObject)); 
                    }

                    const metaString = metaParts.join(' <span class="mx-2 text-slate-300">|</span> ');
                    
                    // --- NEUES DATUMS LABEL DESIGN ---
                    const dObj = new Date(task.date);
                    const dateDisplay = dObj.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' });
                    let dateLabel = '';
                    
                    if (task.type === 'I') {
                         dateLabel = `<div class="flex flex-col items-end"><span class="text-[10px] text-slate-400 uppercase tracking-wider font-bold">Gemeldet</span><span class="text-xs text-slate-600 font-medium">${dateDisplay}</span></div>`;
                    } else {
                         const isLate = dObj < today && task.status !== 'Completed';
                         const colorClass = isLate ? 'text-red-600 font-bold bg-red-50 px-2 py-0.5 rounded' : 'text-slate-600 font-medium';
                         dateLabel = `<div class="flex flex-col items-end"><span class="text-[10px] text-slate-400 uppercase tracking-wider font-bold">Fällig</span><span class="text-xs ${colorClass}">${dateDisplay}</span></div>`;
                    }
                    // ---------------------------------

                    const toggleBtn = hasChildren ? `<button onclick="event.stopPropagation(); toggleTaskRows('${task.id}', this)" class="p-1 -ml-2 mr-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-full transition-colors"><span class="material-icons-outlined text-lg transition-transform duration-200">chevron_right</span></button>` : `<span class="w-6 mr-2 inline-block"></span>`;
                    const paddingLeft = level * 40; const childContainerClass = `child-container-${task.id} hidden`;

                    let prioBadge = '';
                    if (task.type === 'I' && task.priority) { 
                        const p = task.priority; 
                        let pClass = "bg-gray-100 text-gray-600 border-gray-200"; 
                        if (p === 'OPEN') { pClass = "bg-slate-200 text-slate-600 border-slate-300 font-bold border-dashed"; }
                        if (p === 'Medium') pClass = "bg-blue-50 text-blue-700 border-blue-200"; 
                        if (p === 'High') pClass = "bg-orange-50 text-orange-700 border-orange-200"; 
                        if (p === 'Critical') pClass = "bg-red-50 text-red-700 border-red-200 font-bold"; 
                        const pMap = { 'Low': 'Niedrig', 'Medium': 'Mittel', 'High': 'Hoch', 'Critical': 'Kritisch', 'OPEN': 'UNBEWERTET' }; 
                        prioBadge = `<span class="ml-2 px-1.5 py-0.5 rounded-[3px] text-[9px] uppercase tracking-wider border ${pClass}">${pMap[p] || p}</span>`; 
                    }

                    let externBadge = '';
                    if (task.externalCompanyId) { const comp = companies.find(c => c.id === task.externalCompanyId); const compName = comp ? comp.name : 'Unbekannt'; externBadge = `<span class="ml-2 px-1.5 py-0.5 rounded text-[9px] font-bold bg-indigo-100 text-indigo-700 border border-indigo-200 uppercase tracking-wider" title="Ausführung durch: ${compName}">Extern</span>`; }

                    html += `<div class="task-group"><div class="task-row group flex items-center p-3 hover:bg-slate-50 transition-colors cursor-pointer relative" style="padding-left: ${12 + paddingLeft}px;" onclick="openWorkOrderModal('${task.id}')">
                                ${level > 0 ? `<div class="absolute left-0 top-0 bottom-0 border-l border-slate-200" style="left: ${paddingLeft - 18}px;"></div> <div class="absolute w-4 border-t border-slate-200" style="left: ${paddingLeft - 18}px; top: 50%;"></div>` : ''}
                                <div class="flex items-center flex-grow min-w-0"><div class="flex-shrink-0 w-8">${toggleBtn}</div><div class="mr-3 flex-shrink-0">${iconHtml}</div><div class="flex-grow min-w-0"><div class="flex items-center gap-2"><span class="text-sm font-bold text-slate-800 truncate">${task.task}</span>${task.type === 'I' ? `<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-yellow-100 text-yellow-700 border border-yellow-200 uppercase">ISSUE</span>` : ''}${task.type === 'A' ? `<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-green-100 text-green-700 border border-green-200 uppercase">ACTION</span>` : ''}${prioBadge}${externBadge}${indicatorsHtml}</div><div class="text-xs text-slate-500 mt-0.5 flex items-center truncate">${metaString}</div></div></div>
                                <div class="task-meta-col pl-4 text-right">${dateLabel}</div></div>${hasChildren ? `<div class="child-container-${task.id} hidden">${renderRowsRec(childrenMap.get(task.id), level + 1)}</div>` : ''}</div>`;

                });
                return html;
            };
            
            listHtml += renderRowsRec(bucketData, 0); 
            listHtml += `</div>`; 
            body.innerHTML = listHtml;
        };

        // Die Bucket-Render-Aufrufe (mit der rekursiven Zählung, die wir vorhin eingebaut haben)
        renderBucket(buckets.critical, 'groupCritical', 'bodyCritical', 'countCritical');
        renderBucket(buckets.overdue, 'groupOverdue', 'bodyOverdue', 'countOverdue');
        renderBucket(buckets.issues, 'groupIssues', 'bodyIssues', 'countIssues');
        renderBucket(buckets.upcoming, 'groupUpcoming', 'bodyUpcoming', 'countUpcoming');
        renderBucket(buckets.outlook, 'groupOutlook', 'bodyOutlook', 'countOutlook');
        renderBucket(buckets.future, 'groupFuture', 'bodyFuture', 'countFuture');
        
        const emptyState = document.getElementById('emptyStateActive'); 
        if(totalOpenCount === 0) emptyState.classList.remove('hidden'); else emptyState.classList.add('hidden');
        
        // Die Werte in die Kacheln schreiben
        document.getElementById('openTasksCount').textContent = totalOpenCount; 
        document.getElementById('openIssuesCount').textContent = totalIssueCount; 
        document.getElementById('completedTasksCount').textContent = doneCount;

        applyFiltersActive();
    }

    function renderDefinitionsTable() { 
        // 1. Daten in Gruppen sortieren
        const buckets = { weekly: [], monthly: [], yearly: [], other: [] };
        
        taskDefinitions.forEach(def => {
            const unit = (def.intervalUnit || '').toLowerCase();
            if (unit.includes('woche')) { buckets.weekly.push(def); }
            else if (unit.includes('monat')) { buckets.monthly.push(def); }
            else if (unit.includes('jahr')) { buckets.yearly.push(def); }
            else { buckets.other.push(def); }
        });

        // 2. Sortieren innerhalb der Gruppen (Nach Topic, dann nach Titel)
        const sorter = (a, b) => (a.topic || '').localeCompare(b.topic || '') || a.task.localeCompare(b.task);
        Object.values(buckets).forEach(b => b.sort(sorter));

        // 3. Render Helper für eine Gruppe
        const renderGroup = (tasks, bodyId, countId, groupId) => {
            const container = document.getElementById(bodyId);
            const countBadge = document.getElementById(countId);
            const groupDiv = document.getElementById(groupId);
            
            countBadge.textContent = tasks.length;
            
            if (tasks.length === 0) {
                // Leere Gruppen ausblenden (außer vielleicht Sonstige, wenn man will)
                if (groupId === 'groupDefOther') groupDiv.classList.add('hidden');
                container.innerHTML = '<p class="text-center py-4 text-sm text-slate-400 italic">Keine Einträge in dieser Kategorie.</p>';
                return;
            } else {
                groupDiv.classList.remove('hidden');
            }

            // Tabellenkopf
            let html = `
                <table class="w-full text-sm text-left table-fixed"> <!-- table-fixed ist wichtig! -->
                    <thead class="text-xs text-gray-700 uppercase bg-slate-50 border-b border-slate-100">
                        <tr>
                            <th class="px-6 py-3 w-36">ID</th> <!-- Fest 8rem -->
                            <th class="px-6 py-3 w-1/4">Titel</th> <!-- 25% -->
                            <th class="px-6 py-3 w-1/6">Bereich</th> <!-- ~16% -->
                            <th class="px-6 py-3 w-32">Intervall</th>
                            <th class="px-6 py-3 w-1/6">Index/Ort</th>
                            <th class="px-6 py-3 w-24 text-center">Assets</th>
                            <th class="px-6 py-3 w-32 text-center">Extern</th>
                            <th class="px-6 py-3 w-24 text-center">Status</th>
                            <th class="px-6 py-3 w-36 text-right">Aktionen</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-slate-100">`;

            tasks.forEach(def => {
                let locDisplay = def.testbench || ''; 
                if(def.plantIndex) locDisplay += ` (${def.plantIndex})`; 
                const isActive = def.active !== false; 
                const statusBadge = isActive 
                    ? '<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-green-100 text-green-800 border border-green-200">AKTIV</span>' 
                    : '<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 text-slate-500 border border-slate-200">INAKTIV</span>'; 
                
                let externInfo = '-'; 
                if(def.externalCompanyId) { 
                    const comp = companies.find(c => c.id === def.externalCompanyId); 
                    externInfo = comp ? `<span class="text-xs font-medium text-indigo-600 truncate max-w-[100px] block" title="${comp.name}">${comp.name}</span>` : 'Extern'; 
                }

                // Asset Tooltip
                const assets = def.maintenanceObjects || [];
                const assetCount = assets.length;
                let assetHtml = '-';
                if (assetCount > 0) {
                    const tooltipLabels = assets.map(assetStr => {
                        try { if (assetStr.trim().startsWith('{')) return JSON.parse(assetStr).label || assetStr; } catch(e){}
                        return assetStr;
                    });
                    const tooltipText = tooltipLabels.join('\\n'); 
                    assetHtml = `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200 cursor-help" title="${tooltipText}">
                                    <span class="material-icons-outlined text-xs mr-1">inventory_2</span>${assetCount}
                                 </span>`;
                }

                html += `
                    <tr class="hover:bg-slate-50 border-b border-slate-50 transition-colors last:border-0">
                        <td class="px-6 py-3 font-mono text-xs font-medium text-slate-400">${def.id}</td>
                        <td class="px-6 py-3">
                            <span class="font-medium text-slate-800 block">${def.task}</span>
                            ${(def.tags && def.tags.length > 0) ? '<div class="flex flex-wrap gap-1 mt-1">' + def.tags.map(t => `<span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-medium bg-violet-50 text-violet-600 border border-violet-200">${t.startsWith('#') ? t : '#'+t}</span>`).join('') + '</div>' : ''}
                        </td>
                        <td class="px-6 py-3 text-slate-600 text-xs">${def.topic || '-'}</td>
                        <td class="px-6 py-3 text-slate-500 text-xs font-mono">${def.intervalValue} ${def.intervalUnit}</td>
                        <td class="px-6 py-3 text-slate-500 text-xs">${locDisplay}</td>
                        <td class="px-6 py-3 text-center">${assetHtml}</td>
                        <td class="px-6 py-3 text-center">${externInfo}</td>
                        <td class="px-6 py-3 text-center">${statusBadge}</td>
                        <td class="px-6 py-3 text-right space-x-1">
                            <button class="text-slate-400 hover:text-purple-600 p-1 transition-colors" onclick="openHistoryListModal('${def.id}')" title="Historie anzeigen"><span class="material-icons-outlined text-lg">history</span></button>
                            <button class="text-slate-400 hover:text-blue-600 p-1 transition-colors" onclick="openDefinitionModal('${def.id}')" title="Bearbeiten"><span class="material-icons-outlined text-lg">edit</span></button>
                            <button class="text-slate-400 hover:text-red-600 p-1 transition-colors" onclick="deleteDefinition('${def.id}')" title="Löschen"><span class="material-icons-outlined text-lg">delete_outline</span></button>
                        </td>
                    </tr>`;
            });

            html += `</tbody></table>`;
            container.innerHTML = html;
        };

        // 4. Rendern der 4 Gruppen
        renderGroup(buckets.weekly, 'bodyDefWeekly', 'countDefWeekly', 'groupDefWeekly');
        renderGroup(buckets.monthly, 'bodyDefMonthly', 'countDefMonthly', 'groupDefMonthly');
        renderGroup(buckets.yearly, 'bodyDefYearly', 'countDefYearly', 'groupDefYearly');
        renderGroup(buckets.other, 'bodyDefOther', 'countDefOther', 'groupDefOther');

        applyFiltersDefinitions();
    }
    function deleteDefinition(id) { const ordersToDelete = maintenanceOrders.filter(o => { if (o.parentTaskId !== id) return false; if (o.status !== 'Pending') return false; const hasChildren = maintenanceOrders.some(child => child.sourceTask === o.id || child.linkedIssue === o.id); return !hasChildren; }); let confirmMsg = `Wartungsplan ${id} wirklich löschen?`; if (ordersToDelete.length > 0) confirmMsg += `\\n\\nACHTUNG: Es werden automatisch ${ordersToDelete.length} offene (Pending) Aufträge mitgelöscht!`; if(confirm(confirmMsg)) { taskDefinitions = taskDefinitions.filter(d => d.id !== id); deleteDefinitionItem(id); if (ordersToDelete.length > 0) { const idsToDelete = ordersToDelete.map(o => o.id); maintenanceOrders = maintenanceOrders.filter(o => !idsToDelete.includes(o.id)); deleteOrders(idsToDelete); renderOrdersTable(); } renderDefinitionsTable(); } }


    function openWorkOrderModal(id, forceType) {
        if(!id && forceType === 'I') { createLinkedIssue(); return; }
    
        currentOrderId = id;
        const order = maintenanceOrders.find(o => o.id === id);
        if(!order) return;
    
        // --- Navigation / Breadcrumbs ---
        const navParent = document.getElementById('navParentContainer');
        const navChildren = document.getElementById('navChildrenContainer');
        navParent.innerHTML = '';
        navChildren.innerHTML = '';
    
        const parentId = order.sourceTask || order.linkedIssue;
        if (parentId) {
            navParent.innerHTML = `<button onclick="openWorkOrderModal('${parentId}')" class="flex items-center hover:text-blue-600 transition-colors"><span class="material-icons-outlined text-sm mr-1">arrow_back</span> ${parentId}</button>`;
        } else if (order.type === 'M' && order.parentTaskId) {
            const def = taskDefinitions.find(d => d.id === order.parentTaskId);
            const defTitle = def ? def.task : "Unbekannt";
            const defLabel = `Def: ${defTitle} (${order.parentTaskId})`;
            navParent.innerHTML = `<span class="text-slate-500 cursor-default" title="Basierend auf Wartungsplan"><span class="material-icons-outlined text-xs align-middle mr-1">content_paste</span>${defLabel}</span>`;
        } else {
            navParent.innerHTML = '<span class="text-slate-400 cursor-default">Start</span>';
        }
    
        const children = maintenanceOrders.filter(c => c.sourceTask === order.id || c.linkedIssue === order.id);
        if (children.length > 0) {
            children.forEach(child => {
                let colorClass = 'bg-slate-100 text-slate-600 border-slate-200';
                let icon = 'subdirectory_arrow_right';
                if (child.type === 'I') { colorClass = 'bg-yellow-50 text-yellow-700 border-yellow-200'; icon = 'report_problem'; }
                if (child.type === 'A') { colorClass = 'bg-green-50 text-green-700 border-green-200'; icon = 'build'; }
                let check = '';
                if (child.status === 'Completed') { colorClass = 'bg-slate-50 text-slate-400 border-slate-200 line-through decoration-slate-400'; check = '✓ '; }
                const chip = document.createElement('div');
                chip.className = `nav-chip ${colorClass}`;
                chip.title = child.task;
                chip.onclick = () => openWorkOrderModal(child.id);
                chip.innerHTML = `<span class="material-icons-outlined text-[10px] mr-1">${icon}</span> ${check}${child.id}`;
                navChildren.appendChild(chip);
            });
        }
    
        // --- Befüllen der Anzeige- UND Bearbeitungsfelder ---
        document.getElementById('woId').textContent = order.id;
        document.getElementById('woTitle').textContent = order.task;
        
        // Beschreibung
        document.getElementById('woDescriptionViewContainer').textContent = order.description || (order.type === 'I' ? "Keine Beschreibung." : (order.type === 'A' ? "Keine Anweisung." : ""));
        
        // Titel Input (für Edit Mode)
        document.getElementById('woEditTitle').value = order.task;
        
        // --- ZEIT-LOGIK (KORRIGIERT) ---
        
        // 1. Start-Datum (Immer anzeigen)
        const startShiftDisplay = order.startShift || 'AM';
        document.getElementById('woDateDisplay').textContent = `${order.date} (${startShiftDisplay})`;
        
        // 2. Abschluss-Datum (Nur anzeigen, wenn Completed)
        const compDisplay = document.getElementById('woCompletionDisplay');
        
        if (order.status === 'Completed' && order.completedDate) {
            const endShiftDisplay = order.completedShift || 'PM';
            // Pfeil + Check-Icon + Datum
            compDisplay.innerHTML = `<span class="text-slate-400">&rarr;</span> <span class="material-icons-outlined text-sm">check_circle</span> ${order.completedDate} (${endShiftDisplay})`;
            compDisplay.classList.remove('hidden');
        } else {
            // Ausblenden, wenn nicht fertig
            compDisplay.innerHTML = '';
            compDisplay.classList.add('hidden');
        }
        
        // --- EDIT INPUTS INITIALISIEREN ---
        
        // Start Inputs (immer füllen)
        document.getElementById('woEditDate').value = order.date;
        document.getElementById('woEditStartShift').value = startShiftDisplay;
        
        // Abschluss Inputs (immer füllen oder resetten, damit sie bereit sind)
        if (order.completedDate) {
            document.getElementById('woEditCompletedDate').value = order.completedDate;
            document.getElementById('woEditCompletedShift').value = order.completedShift || 'PM';
        } else {
            // WICHTIG: Resetten, damit keine alten Daten drin stehen, wenn man auf "Edit" klickt
            document.getElementById('woEditCompletedDate').value = ''; 
            document.getElementById('woEditCompletedShift').value = 'PM';
        }
        document.getElementById('woEditDescription').value = order.description || '';
        
        // Befüllen der neuen Felder (nur für Issues/Actions relevant)
        if (order.type === 'I' || order.type === 'A') {
            document.getElementById('woEditPriority').value = order.priority || 'Medium';
            // Sicherstellen, dass die Dropdowns existieren bevor wir den Wert setzen
            const categorySelect = document.getElementById('woEditCategory');
            if (categorySelect) categorySelect.value = order.topic || order.category || 'Allgemein';
        }
    
    
        const locString = (order.testbench || '') + (order.plantIndex ? ` (${order.plantIndex})` : '');
        document.getElementById('woTestbench').textContent = locString ? locString : '-';
        document.getElementById('woIndex').textContent = '';
        document.getElementById('woEditPlantIndex').value = order.plantIndex || '';
        document.getElementById('woEditFacilityUnitId').value = order.facilityUnitId || '';
        const topicEl = document.getElementById('woTopic');
        const sepEl = document.getElementById('woTopicSeparator');
        
        if (topicEl && order.topic) {
            topicEl.textContent = order.topic;
            topicEl.parentElement.classList.remove('hidden'); // Parent ist der LocationView, das passt nicht ganz
            // Besser:
            topicEl.style.display = 'inline';
            if(sepEl) sepEl.classList.remove('hidden');
        } else {
            if(topicEl) topicEl.textContent = "";
            if(sepEl) sepEl.classList.add('hidden');
        }
    
        const assetContainer = document.getElementById('woAssetTags');
        assetContainer.innerHTML = '';
        let assets = order.maintenanceObjects || (order.maintenanceObject ? [order.maintenanceObject] : []);
        if ((order.type === 'I' || order.type === 'A') && (!assets || assets.length === 0)) {
             assets = order.affectedAssets || (order.affectedAsset ? [order.affectedAsset] : []);
        }
        if (assets.length > 0) {
            assets.forEach(asset => {
                let displayLabel = asset;
                let assetId = null;
                try {
                    if (asset.trim().startsWith('{')) {
                        const obj = JSON.parse(asset);
                        displayLabel = obj.label || asset;
                        assetId = obj.id || null;
                    }
                } catch(e) {}

                if (assetId) {
                    assetContainer.innerHTML += `<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-white text-slate-600 border border-slate-200 text-[12px] font-mono tracking-tight shadow-sm cursor-pointer hover:bg-blue-50 hover:border-blue-300 hover:text-blue-700 transition-colors" title="Im Inventar öffnen" onclick="window.open('/test_equipment?hwid=${assetId}','_blank')"><span class="material-icons-outlined text-[10px] text-slate-400">precision_manufacturing</span>${displayLabel}<span class="material-icons-outlined text-[9px] text-blue-400">open_in_new</span></span>`;
                } else {
                    assetContainer.innerHTML += `<span class="inline-flex items-center px-2 py-0.5 rounded bg-white text-slate-600 border border-slate-200 text-[12px] font-mono tracking-tight shadow-sm cursor-help" title="Asset: ${displayLabel}"><span class="material-icons-outlined text-[10px] text-slate-400 mr-1">precision_manufacturing</span>${displayLabel}</span>`;
                }
            });
        } else {
            assetContainer.innerHTML = '';
        }
    
        const prioBadge = document.getElementById('woPrioBadge');
        const reviewIcon = document.getElementById('woReviewIcon');
        const btnAction = document.getElementById('btnCreateAction');
        const btnIssue = document.getElementById('btnCreateIssue');
        const btnDelete = document.getElementById('btnDeleteOrder');
        const btnTriage = document.getElementById('btnTriage');
        btnAction.classList.add('hidden'); btnIssue.classList.add('hidden'); btnTriage.classList.add('hidden');
        const btnEdit = document.getElementById('btnEditOrder');
            if(btnEdit) {
                btnEdit.onclick = () => switchToWoEditMode();

                const isUnevaluatedIssue = order.type === 'I' && order.priority === 'OPEN';
                const isProposedAction = order.type === 'A' && order.status === 'Proposed';

                if (isUnevaluatedIssue || (!canEditMaintenance && !isProposedAction)) {
                    btnEdit.style.display = 'none';
                } else if (isProposedAction) {
                    // Only reviewers can edit a Proposed action
                    btnEdit.style.display = canUserReview(order) ? 'inline-block' : 'none';
                } else {
                    const canEdit = order.status !== 'Completed' || currentUserIsAdmin;
                    btnEdit.style.display = canEdit ? 'inline-block' : 'none';
                }
            }
    
        prioBadge.innerHTML = '';
        if(order.type === 'M') { btnIssue.classList.remove('hidden'); } 
        else if(order.type === 'I') { 
            if (order.priority === 'OPEN') {
                 prioBadge.className = "px-2 py-0.5 rounded text-xs font-bold bg-slate-200 text-slate-600 tracking-wider border border-slate-300";
                 prioBadge.textContent = "UNBEWERTET";
                 btnTriage.classList.remove('hidden');
            } else {
                let pClass = 'bg-gray-100 text-gray-800';
                if(order.priority === 'Medium') pClass = 'bg-yellow-100 text-yellow-800';
                if(order.priority === 'High') pClass = 'bg-orange-100 text-orange-800';
                if(order.priority === 'Critical') pClass = 'bg-red-100 text-red-800';
                prioBadge.className = `px-2 py-0.5 rounded text-xs font-bold tracking-wider ${pClass}`;
                prioBadge.textContent = order.priority.toUpperCase();
                btnAction.classList.remove('hidden'); 
            }
        } else if(order.type === 'A') { 
            prioBadge.className = "px-2 py-0.5 rounded text-xs font-bold bg-green-100 text-green-700 tracking-wider border border-green-200"; 
            prioBadge.textContent = "ACTION";
        }
    
        if (order.requiresReview === true || order.requiresReview === "true") { reviewIcon.classList.remove('hidden'); } else { reviewIcon.classList.add('hidden'); }
        if (order.status === 'In Progress') { btnIssue.disabled = false; btnIssue.classList.remove('opacity-50', 'cursor-not-allowed'); btnAction.disabled = false; btnAction.classList.remove('opacity-50', 'cursor-not-allowed'); } else { btnIssue.disabled = true; btnIssue.classList.add('opacity-50', 'cursor-not-allowed'); btnAction.disabled = true; btnAction.classList.add('opacity-50', 'cursor-not-allowed'); }
        if (!canEditMaintenance || (order.status === 'Completed' && !currentUserIsAdmin)) { btnDelete.style.display = 'none'; } else { btnDelete.style.display = 'inline-block'; }
    
        const clContainer = document.getElementById('woChecklistContainer');
        clContainer.innerHTML = '';
        if (order.type !== 'I' && order.type !== 'A') {
            document.getElementById('woChecklistSection').classList.remove('hidden');
            const isExecutionActive = (order.status === 'In Progress');
            if (order.checklist && order.checklist.length > 0) {
                order.checklist.forEach((item, idx) => {
                    // Parse {value} or {value:unit} marker
                    const valueMatch = item.match(/\{value(?::([^}]*))?\}/);
                    const hasValue = !!valueMatch;
                    const unit = hasValue && valueMatch[1] ? valueMatch[1].trim() : '';
                    const labelText = item.replace(/\{value(?::[^}]*)?\}/, '').trim();

                    const state = order.checklistState && order.checklistState[idx];
                    const isChecked = hasValue ? (state && state.checked === true) : (state === true);
                    const savedValue = hasValue && state ? (state.value || '') : '';

                    const checkedAttr = isChecked ? 'checked' : '';
                    const disabledAttr = isExecutionActive ? '' : 'disabled';
                    const cursorClass = isExecutionActive ? 'cursor-pointer' : 'cursor-not-allowed';

                    const div = document.createElement('div');
                    div.className = `checklist-item ${isChecked ? 'checked' : ''} ${cursorClass}`;
                    div.id = `chk-item-${idx}`;

                    if (hasValue) {
                        // Checkbox is enabled only when input has a value
                        const cbDisabled = isExecutionActive && savedValue ? '' : 'disabled';
                        div.innerHTML = `
                            <label class="flex items-center gap-2 flex-1 min-w-0 ${cursorClass}">
                                <input type="checkbox" id="chk-cb-${idx}" ${checkedAttr} ${isExecutionActive ? '' : 'disabled'} onchange="toggleChecklistItem(${idx}, this.checked)" class="${cursorClass} transition-colors flex-shrink-0">
                                <span class="text-sm select-none">${labelText}</span>
                            </label>
                            <div class="flex items-center gap-1 mt-1 ml-6">
                                <input type="text" id="chk-val-${idx}" value="${savedValue}" placeholder="Wert eingeben..." ${isExecutionActive && !isChecked ? '' : 'disabled'}
                                    class="border border-slate-300 rounded px-2 py-0.5 text-sm w-32 focus:ring-1 focus:ring-blue-400 focus:outline-none ${isExecutionActive && !isChecked ? '' : 'bg-slate-50 text-slate-400'}"
                                    oninput="onChecklistValueInput(${idx}, this.value)">
                                ${unit ? `<span class="text-xs text-slate-500 font-medium">${unit}</span>` : ''}
                            </div>`;
                    } else {
                        div.innerHTML = `<label class="flex items-center gap-2 w-full ${cursorClass}"><input type="checkbox" ${checkedAttr} ${disabledAttr} onchange="toggleChecklistItem(${idx}, this.checked)" class="${cursorClass} transition-colors"><span class="text-sm select-none flex-grow">${labelText}</span></label>`;
                    }
                    clContainer.appendChild(div);
                });
            } else { clContainer.innerHTML = '<span class="text-xs text-slate-400 italic">Keine Checkliste definiert.</span>'; }
        } else { document.getElementById('woChecklistSection').classList.add('hidden'); }
    
        const partsContainer = document.getElementById('woConsumables');
        partsContainer.innerHTML = '';
        if (order.type !== 'I') {
             document.getElementById('woConsumablesSection').classList.remove('hidden');
             let rawCons = [];
             if (order.consumablesList && order.consumablesList.length > 0) {
                 rawCons = order.consumablesList;
             } else if (order.consumables) {
                 rawCons = order.consumables.split(',');
             } else if (order.spareParts) {
                 rawCons = order.spareParts.split(',');
             }
             let cons = rawCons.map(s => s.trim()).filter(s => s);
             if(cons.length > 0) {
                cons.forEach(p => { 
                    partsContainer.innerHTML += `<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-mono bg-slate-100 text-slate-600 border border-slate-200">${p}</span>`; 
                });
             } else { 
                 partsContainer.innerHTML = '<span class="text-xs text-slate-400 italic">Kein Material.</span>'; 
             }
        } else {
             document.getElementById('woConsumablesSection').classList.add('hidden');}
    
        const attachContainer = document.getElementById('woAttachmentsView');
        attachContainer.innerHTML = '';
        
        let attachments = [];
        // 1. Anhänge aus der Definition holen (falls es eine Wartung ist)
        if(order.type === 'M' && order.parentTaskId) { 
            const def = taskDefinitions.find(d => d.id === order.parentTaskId); 
            if(def) { 
                if(def.images) attachments.push(...def.images); 
                if(def.documents) attachments.push(...def.documents); 
            } 
        }
        // 2. Anhänge aus dem Auftrag selbst (z.B. Fotos bei Issues)
        if (order.attachments) attachments.push(...order.attachments);
    
        if(attachments.length > 0) {
            attachments.forEach(a => { 
                const div = document.createElement('div'); 
                div.className = 'attachment-item group hover:border-blue-300 transition-colors'; 
                
                const url = a.dataUrl || a.path;
                const isImage = url && (
                    url.startsWith('data:image') ||
                    /\.(jpg|jpeg|png|gif|webp)$/i.test(a.name)
                );

                if(isImage) {
                    div.innerHTML = `
                        <img src="${url}" class="cursor-zoom-in hover:opacity-80 transition-opacity"
                             onclick="event.stopPropagation(); openLightbox('${url}', '${a.name}')">
                        <span class="truncate text-xs cursor-pointer hover:text-blue-600"
                              onclick="openLightbox('${url}', '${a.name}')">${a.name}</span>
                    `;
                } else {
                    div.innerHTML = `
                        <a href="${url}" target="_blank" class="flex items-center gap-2 w-full text-slate-600 hover:text-blue-600">
                            <span class="material-icons-outlined text-slate-400">description</span>
                            <span class="truncate text-xs">${a.name}</span>
                        </a>
                    `;
                } 
                attachContainer.appendChild(div); 
            });
        } else { 
            attachContainer.innerHTML = '<p class="text-xs text-slate-400 italic">Keine Dokumente.</p>'; 
        }
        renderStatusActions(order);
        renderAuditTrail(order);
        
        // Sicherstellen, dass der Ansichtsmodus aktiv ist, wenn das Modal geöffnet wird
        switchToWoViewMode();
        
        // --- NEU: Blocker Badges rendern ---
        const blockerContainer = document.getElementById('woBlockerBadges');
        blockerContainer.innerHTML = '';
    
        if (order.isUnitBlocker === true || order.isUnitBlocker === "true") {
            blockerContainer.innerHTML += `<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-red-600 text-white border border-red-700 uppercase tracking-wider shadow-sm">ANLAGE GESPERRT</span>`;
        }
        
        if (order.blockedAssetIds && order.blockedAssetIds.length > 0) {
            blockerContainer.innerHTML += `<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-orange-100 text-orange-800 border border-orange-200 uppercase tracking-wider">HW SPERRE (${order.blockedAssetIds.length})</span>`;
        }
        // Legacy Fallback
        if ((order.isBlocker === true || order.isBlocker === "true") && !order.isUnitBlocker && (!order.blockedAssetIds || order.blockedAssetIds.length === 0)) {
             blockerContainer.innerHTML += `<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-red-100 text-red-700 border border-red-200 uppercase tracking-wider">BLOCKER</span>`;
        }
    
        openModal('workOrderModal');
    }
    function renderEditBlockerList(order, currentAssetTags) {
        const list = document.getElementById('woEditBlockedAssetsList');
        list.innerHTML = '';
        
        // Aktuell gespeicherte Sperren
        const blockedSet = new Set(order.blockedAssetIds || []);
        
        if (currentAssetTags.length === 0) {
            list.innerHTML = '<p class="text-xs text-slate-400 italic">Keine Assets verknüpft.</p>';
            return;
        }
    
        currentAssetTags.forEach(assetLabel => {
            // Wir müssen die ID finden (über das globale Inventar)
            const inventoryItem = hardwareInventoryList.find(h => h.label === assetLabel);
            
            if (inventoryItem && inventoryItem.id) {
                const isChecked = blockedSet.has(inventoryItem.id) ? 'checked' : '';
                const div = document.createElement('label');
                div.className = "flex items-center gap-2 hover:bg-slate-50 p-1 rounded cursor-pointer";
                div.innerHTML = `
                    <input type="checkbox" value="${inventoryItem.id}" ${isChecked} class="edit-blocker-cb rounded border-gray-300 text-orange-600 focus:ring-orange-500 w-4 h-4">
                    <span class="text-xs text-slate-700 truncate">${assetLabel}</span>
                `;
                list.appendChild(div);
            } else {
                // Asset ohne ID (Freitext) kann nicht technisch geblockt werden, nur informativ anzeigen?
                // Wir lassen es hier weg, da wir den Status nicht in der DB setzen können.
            }
        });
    }

    function onChecklistValueInput(index, val) {
        const cb = document.getElementById(`chk-cb-${index}`);
        if (cb) cb.disabled = !val.trim();
        if (!val.trim() && cb && cb.checked) {
            cb.checked = false;
            toggleChecklistItem(index, false);
        }
    }

    function toggleChecklistItem(index, isChecked) {
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;
        if (order.status !== 'In Progress') {
            const checkboxes = document.querySelectorAll('#woChecklistContainer input[type="checkbox"]');
            if(checkboxes[index]) { checkboxes[index].checked = !isChecked; }
            alert("Checklisten können nur bearbeitet werden, wenn der Auftrag den Status 'In Progress' hat.");
            return;
        }
        if (!order.checklistState || typeof order.checklistState !== 'object') { order.checklistState = {}; }

        const rawItem = (order.checklist && order.checklist[index]) ? order.checklist[index] : '';
        const valueMatch = rawItem.match(/\{value(?::([^}]*))?\}/);
        const hasValue = !!valueMatch;
        const unit = hasValue && valueMatch[1] ? valueMatch[1].trim() : '';
        const labelText = rawItem.replace(/\{value(?::[^}]*)?\}/, '').trim() || `#${index + 1}`;

        if (hasValue) {
            const valInput = document.getElementById(`chk-val-${index}`);
            const enteredValue = valInput ? valInput.value.trim() : '';
            if (isChecked && !enteredValue) {
                const cb = document.getElementById(`chk-cb-${index}`);
                if (cb) cb.checked = false;
                return;
            }
            order.checklistState[index] = isChecked ? { checked: true, value: enteredValue } : { checked: false, value: enteredValue };
            if (valInput) {
                valInput.disabled = isChecked;
                if (isChecked) { valInput.classList.add('bg-slate-50', 'text-slate-400'); }
                else { valInput.classList.remove('bg-slate-50', 'text-slate-400'); }
            }
            const action = isChecked ? 'abgehakt' : 'zurückgesetzt';
            const valueStr = enteredValue ? ` → ${enteredValue}${unit ? ' ' + unit : ''}` : '';
            order.logs.push({ user: currentUser, timestamp: new Date().toLocaleString(), note: `Checkpoint ${action}: "${labelText}"${valueStr}` });
        } else {
            order.checklistState[index] = isChecked;
            const action = isChecked ? 'abgehakt' : 'zurückgesetzt';
            order.logs.push({ user: currentUser, timestamp: new Date().toLocaleString(), note: `Checkpoint ${action}: "${labelText}"` });
        }

        const row = document.getElementById(`chk-item-${index}`);
        if (row) { if (isChecked) row.classList.add('checked'); else row.classList.remove('checked'); }
        saveOrder(order);
        renderAuditTrail(order);
    }

    function closeWorkOrderModal() { document.getElementById('workOrderModal').classList.add('hidden'); }
    function renderStatusActions(order) { 
        const badge = document.getElementById('woStatusBadge'); 
        const actions = document.getElementById('statusActions'); 
        actions.innerHTML = ''; 

        // Hilfsfunktion für den neuen Button-Stil
        // farbe: 'blue', 'green', 'yellow', 'red', 'purple'
        const createBtn = (label, icon, color, onclick, disabled = false) => {
            const opacity = disabled ? 'opacity-50 cursor-not-allowed' : 'hover:shadow-md active:scale-[0.98] transition-all';
            return `
            <button onclick="${onclick}" ${disabled ? 'disabled' : ''} 
                class="w-full py-3 rounded-lg border-2 border-${color}-100 bg-${color}-50 text-${color}-700 font-bold uppercase tracking-wider flex items-center justify-center gap-2 ${opacity}">
                <span class="material-icons-outlined text-lg">${icon}</span>
                ${label}
            </button>`;
        };

        // Leerer Platzhalter für das Grid Layout
        const emptySlot = `<div></div>`;

        if (order.type === 'A' && order.status === 'Proposed') {
            badge.className = "px-3 py-1 rounded-full text-xs font-bold bg-orange-100 text-orange-800 border border-orange-200";
            badge.textContent = "VORGESCHLAGEN";
            const canApprove = canUserReview(order);
            if (canApprove) {
                actions.innerHTML = `
                    ${createBtn('Ablehnen', 'delete_forever', 'red', "deleteProposedAction()")}
                    ${createBtn('Freigeben', 'check_circle', 'green', "changeStatus('Pending')")}
                `;
            } else {
                actions.innerHTML = `
                    <div class="col-span-2 flex items-center justify-center p-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-400 text-xs italic">
                        <span class="material-icons-outlined text-sm mr-2">lock</span>
                        Warten auf Freigabe durch verantwortliches Engineering
                    </div>
                `;
            }

        } else if (order.status === 'Completed') {
            badge.className = "px-3 py-1 rounded-full text-xs font-bold bg-green-100 text-green-800 border border-green-200"; 
            badge.textContent = "COMPLETED"; 
            
            // Layout: Links Wiedereröffnen, Rechts Leer (oder andersrum, je nach Wunsch)
            // Hier: Wiedereröffnen Links
            actions.innerHTML = `
                ${createBtn('Wieder öffnen', 'replay', 'slate', "changeStatus('In Progress')")}
                ${emptySlot}
            `;

        } else if (order.status === 'Review Ready') { 
            badge.className = "px-3 py-1 rounded-full text-xs font-bold bg-purple-100 text-purple-800 border border-purple-200"; 
            badge.textContent = "REVIEW READY"; 
            
            // NEU: Berechtigungs-Check
            const canApprove = canUserReview(order);
            
            // Wenn der User es selbst ausgeführt hat, darf er (in strengen Systemen) nicht selbst prüfen.
            // Sollen wir das erzwingen? (order.executedBy !== currentUserId)
            // Für jetzt lassen wir es offen, aber der Check ist hier möglich.
            
            actions.innerHTML = `
                ${createBtn('Ablehnen', 'thumb_down', 'red', "changeStatus('In Progress')", !canApprove)}
                ${createBtn('Freigabe', 'verified', 'purple', "changeStatus('Completed')", !canApprove)}
            `;

        } else if (order.status === 'In Progress') { 
            badge.className = "px-3 py-1 rounded-full text-xs font-bold bg-blue-100 text-blue-800 border border-blue-200"; 
            badge.textContent = "IN PROGRESS"; 
            
            const finishLabel = order.requiresReview ? "Zur Prüfung" : "Abschließen"; 
            const finishIcon = order.requiresReview ? "send" : "check_circle"; 
            
            // Layout: Links Pause (Gelb), Rechts Fertig (Grün)
            actions.innerHTML = `
                ${createBtn('Pause', 'pause', 'yellow', "changeStatus('Pending')")}
                ${createBtn(finishLabel, finishIcon, 'green', "changeStatus('Completed')")}
            `;

        } else { 
            // START ZUSTAND (Pending / OPEN)
            let btnText = "Starten"; 
            if (order.status === 'OPEN') btnText = " Starten"; 
            
            badge.className = "px-3 py-1 rounded-full text-xs font-bold bg-yellow-100 text-yellow-800 border border-yellow-200"; 
            badge.textContent = order.status.toUpperCase(); 
            
            if (order.type === 'I' && order.priority === 'OPEN') {
                const canTriage = canUserReview(order);
                
                if (canTriage) {
                    // Darf bewerten -> Button zeigen
                    btnTriage.classList.add('hidden');
                    
                    // Wir zeigen den Button im Grid anstelle des Info-Textes
                    actions.innerHTML = `
                        <div class="col-span-2">
                             <button onclick="openTriageModal()" class="w-full py-3 rounded-lg border-2 border-indigo-100 bg-indigo-50 text-indigo-700 font-bold uppercase tracking-wider flex items-center justify-center gap-2 hover:bg-indigo-100 transition-colors">
                                <span class="material-icons-outlined text-lg">rule</span>
                                Issue Bewerten
                            </button>
                        </div>`;
                } else {
                    // Darf NICHT bewerten -> Info Text
                    btnTriage.classList.add('hidden');
                    actions.innerHTML = `
                        <div class="col-span-2 flex items-center justify-center p-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-400 text-xs italic">
                            <span class="material-icons-outlined text-sm mr-2">lock</span>
                            Warten auf Bewertung durch verantwortliches Engineering
                        </div>`;
                }
            } else {
                // Layout: Links Leer, Rechts Starten (Blau)
                actions.innerHTML = `
                    ${emptySlot}
                    ${createBtn(btnText, 'play_arrow', 'blue', "changeStatus('In Progress')")}
                `;
            }
        } 
    }
    function renderAuditTrail(order) {
        const c = document.getElementById('auditTrailContainer');
        c.innerHTML = '';
        const logs = order.logs || [];

        if(logs.length === 0) {
            c.innerHTML = '<div class="flex h-full items-center justify-center text-slate-400 text-sm italic">Keine Einträge.</div>';
            return;
        }

        [...logs].reverse().forEach((l, revIdx) => {
            const realIdx = logs.length - 1 - revIdx;
            const isSys = l.user === 'System';
            const div = document.createElement('div');
            div.className = `flex flex-col gap-1 ${isSys ? 'items-center my-4' : 'items-start mb-4'}`;

            let dateDisplay = l.timestamp;
            try {
                if (dateDisplay.includes(',')) {
                    dateDisplay = dateDisplay.split(',')[0];
                } else {
                    const d = new Date(l.timestamp);
                    if (!isNaN(d.getTime())) dateDisplay = d.toLocaleDateString('de-DE');
                }
            } catch(e) {}

            if(isSys) {
                div.innerHTML = `<span class="text-[10px] font-bold text-slate-400 uppercase tracking-wider bg-slate-100 px-2 py-0.5 rounded-full border border-slate-200">${l.note} &bull; ${dateDisplay}</span>`;
            } else if(l.attachment) {
                const att = l.attachment;
                const isImage = att.name && /\.(jpe?g|png|gif|webp|bmp)$/i.test(att.name);
                const canDelete = currentUserIsAdmin || l.user === currentUser;
                const deleteBtn = canDelete
                    ? `<button onclick="deleteLogAttachment(${realIdx})" class="ml-auto text-slate-300 hover:text-red-500 transition-colors p-1 flex-shrink-0" title="Anhang löschen"><span class="material-icons-outlined text-base">delete</span></button>`
                    : '';
                const preview = isImage
                    ? `<a href="${att.path}" target="_blank"><img src="${att.path}" class="mt-2 max-h-40 rounded border border-slate-200 object-contain" alt="${att.name}"></a>`
                    : `<a href="${att.path}" target="_blank" class="flex items-center gap-2 mt-2 text-blue-600 hover:underline text-xs"><span class="material-icons-outlined text-base">description</span>${att.name}</a>`;
                div.innerHTML = `
                    <div class="flex items-baseline gap-2">
                        <span class="text-xs font-bold text-slate-700">${l.user}</span>
                        <span class="text-[10px] text-slate-400">${dateDisplay}</span>
                    </div>
                    <div class="bg-white p-3 rounded-tr-lg rounded-br-lg rounded-bl-lg border border-slate-200 shadow-sm max-w-[90%] w-full">
                        <div class="flex items-center gap-2 text-xs text-slate-500 italic">
                            <span class="material-icons-outlined text-sm text-slate-400">attach_file</span>
                            <span>${att.name}</span>
                            ${deleteBtn}
                        </div>
                        ${preview}
                    </div>`;
            } else {
                const canDeleteComment = currentUserIsAdmin;
                const deleteCommentBtn = canDeleteComment
                    ? `<button onclick="deleteLogComment(${realIdx})" class="ml-2 text-slate-300 hover:text-red-500 transition-colors p-0.5 flex-shrink-0" title="Kommentar löschen"><span class="material-icons-outlined text-base">delete</span></button>`
                    : '';
                div.innerHTML = `
                    <div class="flex items-baseline gap-2">
                        <span class="text-xs font-bold text-slate-700">${l.user}</span>
                        <span class="text-[10px] text-slate-400">${dateDisplay}</span>
                        ${deleteCommentBtn}
                    </div>
                    <div class="bg-white p-3 rounded-tr-lg rounded-br-lg rounded-bl-lg border border-slate-200 shadow-sm text-sm text-slate-800 whitespace-pre-wrap max-w-[90%]">${l.note}</div>`;
            }
            c.appendChild(div);
        });
    }

    function addLogEntry() { const input = document.getElementById('logNoteInput'); const val = input.value.trim(); if(!val) return; const order = maintenanceOrders.find(o => o.id === currentOrderId); if(order) { order.logs.push({ user: currentUser, timestamp: new Date().toLocaleString(), note: val }); saveOrder(order); renderAuditTrail(order); input.value = ''; } }

    async function addLogAttachments(input) {
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;
        for (const file of input.files) {
            const result = await handleUpload(file);
            if (result) {
                order.logs.push({
                    user: currentUser,
                    timestamp: new Date().toLocaleString(),
                    note: `Datei angehängt: ${file.name}`,
                    attachment: { name: file.name, path: result.path }
                });
            }
        }
        input.value = '';
        await saveOrder(order);
        renderAuditTrail(order);
    }

    async function deleteLogAttachment(logIndex) {
        if (!confirm('Anhang dauerhaft löschen?')) return;
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;
        const entry = order.logs[logIndex];
        if (!entry || !entry.attachment) return;
        const filename = entry.attachment.path.split('/').pop();
        try {
            await fetch(`/api/maintenance/upload/${encodeURIComponent(filename)}`, { method: 'DELETE' });
        } catch(e) { console.warn('Server-seitiges Löschen fehlgeschlagen', e); }
        order.logs.splice(logIndex, 1);
        await saveOrder(order);
        renderAuditTrail(order);
    }

    async function deleteLogComment(logIndex) {
        if (!confirm('Kommentar dauerhaft löschen?')) return;
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;
        order.logs.splice(logIndex, 1);
        await saveOrder(order);
        renderAuditTrail(order);
    }

    async function deleteProposedAction() {
        if (!confirm('Vorgeschlagene Maßnahme ablehnen und dauerhaft löschen?')) return;
        maintenanceOrders = maintenanceOrders.filter(o => o.id !== currentOrderId);
        await deleteOrder(currentOrderId);
        closeWorkOrderModal();
        renderOrdersTable();
    }

    async function changeStatus(newStatus) {
        const order = maintenanceOrders.find(o => o.id === currentOrderId);
        if (!order) return;

        const wasBlockerIssue = order.type === 'I' && order.isBlocker;

        if (newStatus === 'Completed' && order.requiresReview && order.status !== 'Review Ready') {
            newStatus = 'Review Ready';
            order.executedBy = currentUserId;
            order.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Fertig gemeldet. Warten auf Freigabe.` });
        }
        if (newStatus === 'Completed' || newStatus === 'Review Ready') {
            if (order.type === 'M' && order.parentTaskId && order.date) {
                const def = taskDefinitions.find(d => d.id === order.parentTaskId);
                if (def) {
                    const intervalInDays = parseInt(def.intervalValue) * (def.intervalUnit === 'Jahre' ? 365 : def.intervalUnit === 'Monate' ? 30 : 7);
                    const threshold = Math.max(7, intervalInDays * 0.25);
                    const today = new Date(); today.setHours(0, 0, 0, 0);
                    const dueDate = new Date(order.date); dueDate.setHours(0, 0, 0, 0);
                    const daysUntilDue = Math.round((dueDate - today) / 86400000);
                    if (daysUntilDue > threshold) {
                        if (!confirm(`Dieser Auftrag ist noch ${daysUntilDue} Tage nicht fällig. Der Abschluss erzeugt automatisch einen neuen Folgeauftrag. Trotzdem jetzt abschließen?`)) return;
                    }
                }
            }
            if (order.checklist && order.checklist.length > 0) {
                const allChecked = order.checklist.every((_, idx) => { const s = order.checklistState && order.checklistState[idx]; return s === true || (s && s.checked === true); });
                if (!allChecked) {
                    if (!confirm("Die Checkliste ist noch nicht vollständig abgehakt. Trotzdem abschließen?")) return;
                }
            }
        }
        if (order.status === 'Review Ready' && newStatus === 'In Progress') {
            order.logs.push({ user: currentUser, timestamp: new Date().toLocaleString(), note: `Freigabe abgelehnt. Zurück zur Bearbeitung.` });
        } else if (newStatus !== 'Review Ready') {
            order.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Status geändert zu ${newStatus}` });
        }
        order.status = newStatus;
        let definitionsHaveChanged = false;
    
        // ... (Logik für M-Type Completed & Folgeauftrag bleibt identisch) ...
        if (newStatus === 'Completed') {
            order.completedBy = currentUser;
            order.completedDate = new Date().toISOString().split('T')[0];
            
            if (order.type === 'M' && order.parentTaskId) {
                const def = taskDefinitions.find(d => d.id === order.parentTaskId);
                if (def && def.active !== false) {
                    if (def.intervalType === 'fixed') {
                        const nextDate = calculateNextFixedRhythmDate(def, order.date);
                        createOrderFromDef(def.id, nextDate, true);
                        order.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Folgeauftrag für ${nextDate} geplant (Fester Rhythmus).` });
                    } else {
                        const nextDate = calculateNextDueDate(def.intervalValue, def.intervalUnit, order.completedDate);
                        createOrderFromDef(def.id, nextDate, true);
                        def.startDate = order.completedDate;
                        order.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Folgeauftrag für ${nextDate} geplant.` });
                    }
                    definitionsHaveChanged = true;
                }
            }
        }
    
        // ... (Logik für A-Type Linked Issue bleibt identisch) ...
        let parentIssueToSave = null;
        if ((newStatus === 'Completed' || newStatus === 'Review Ready') && order.type === 'A' && order.linkedIssue) {
            const parentIssue = maintenanceOrders.find(p => p.id === order.linkedIssue);
            if (parentIssue && parentIssue.status !== 'Completed') {
                const siblingActions = maintenanceOrders.filter(s => s.linkedIssue === parentIssue.id && s.id !== order.id);
                const allSiblingsDone = siblingActions.every(s => s.status === 'Completed');
                if (allSiblingsDone) {
                    if (parentIssue.status !== 'Review Ready') {
                        parentIssue.status = 'Review Ready';
                        parentIssue.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Alle Maßnahmen abgeschlossen. Status auf 'Review Ready' gesetzt.` });
                        parentIssueToSave = parentIssue;
                    }
                }
            }
        }

        if (definitionsHaveChanged) {
            const changedDef = taskDefinitions.find(d => d.id === order.parentTaskId);
            if (changedDef) await saveDefinitionItem(changedDef);
        }
        await saveOrder(order);
        if (parentIssueToSave) {
            await saveOrder(parentIssueToSave);
        }
        
        // --- NEU: HARDWARE STATUS RESET (UUID-BASIERT) ---
        if (newStatus === 'Completed' && wasBlockerIssue) {
            const targetIds = [];
            
            (order.affectedAssets || []).forEach(assetStr => {
                try {
                    if (assetStr.trim().startsWith('{')) {
                        const obj = JSON.parse(assetStr);
                        if (obj.id) targetIds.push(obj.id);
                    }
                } catch(e) {}
            });

            if (targetIds.length > 0) {
                try {
                    const response = await fetch('/api/hardware/set_status', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({
                            ids: targetIds, // Sende IDs
                            status: 'Available'
                        })
                    });
                    if (!response.ok) { throw new Error('Server-Antwort war nicht OK.'); }
                    console.log("Hardware-Status erfolgreich zurückgesetzt.");
                } catch (error) {
                    console.error("Fehler:", error);
                    alert("Hinweis: Hardware-Status konnte nicht automatisch zurückgesetzt werden.");
                }
            }
        }
        // -------------------------------------------------
   
        renderStatusActions(order);
        renderAuditTrail(order);
        renderOrdersTable();
        renderDefinitionsTable();
        openWorkOrderModal(currentOrderId);
    }
    function deleteCurrentOrder() { if (!currentOrderId) return; const order = maintenanceOrders.find(o => o.id === currentOrderId); if (!order) return; if (order.status === 'Completed' && !currentUserIsAdmin) { alert("Abgeschlossene Aufträge können nur von Admins gelöscht werden."); return; } if (confirm(`Möchten Sie den Auftrag "${order.task}" (${order.id}) wirklich unwiderruflich löschen?`)) { const idToDelete = currentOrderId; maintenanceOrders = maintenanceOrders.filter(o => o.id !== currentOrderId); deleteOrder(idToDelete); closeWorkOrderModal(); renderOrdersTable(); } }
    function openTriageModal() { 
        const order = maintenanceOrders.find(o => o.id === currentOrderId); 
        if(!order) return; 
        
        document.getElementById('triageIssueTitle').textContent = order.task; 
        document.getElementById('triageIssueDesc').textContent = order.description || "Keine Beschreibung"; 
        
        // Prio setzen
        const currentPrio = order.priority || "OPEN";
        const radio = document.querySelector(`input[name="triagePriority"][value="${currentPrio}"]`);
        if(radio) radio.checked = true;
        else document.querySelectorAll('input[name="triagePriority"]').forEach(r => r.checked = false);
    
        // Unit Blocker setzen (Rückwärtskompatibel: altes 'isBlocker' wird als Unit Blocker interpretiert, falls isUnitBlocker fehlt)
        const isUnitBlocked = order.isUnitBlocker === true || order.isUnitBlocker === "true" || order.isBlocker === true;
        document.getElementById('triageUnitBlocker').checked = isUnitBlocked; 
    
        // Hardware Liste bauen
        const assetSection = document.getElementById('triageAssetSection');
        const assetList = document.getElementById('triageAssetList');
        assetList.innerHTML = '';
    
        const assets = [];
        if (order.affectedAssets) assets.push(...order.affectedAssets);
        if (order.maintenanceObjects) assets.push(...order.maintenanceObjects);
    
        if (assets.length > 0) {
            assetSection.classList.remove('hidden');
            const blockedIds = new Set(order.blockedAssetIds || []); // Bereits gesperrte IDs
    
            assets.forEach(assetStr => {
                let label = assetStr;
                let id = null;
                try {
                    if (assetStr.trim().startsWith('{')) {
                        const obj = JSON.parse(assetStr);
                        label = obj.label;
                        id = obj.id;
                    } else {
                        // Fallback für alte Strings: ID im globalen Inventar suchen
                        const item = hardwareInventoryList.find(h => h.label === assetStr);
                        if(item) id = item.id;
                    }
                } catch(e){}
    
                if (id) {
                    const isChecked = blockedIds.has(id) ? 'checked' : '';
                    const div = document.createElement('div');
                    div.className = "flex items-center gap-3 p-2 bg-white border border-slate-200 rounded hover:bg-blue-50";
                    div.innerHTML = `
                        <input type="checkbox" value="${id}" ${isChecked} class="triage-asset-cb w-4 h-4 text-orange-600 border-gray-300 rounded focus:ring-orange-500">
                        <span class="text-sm text-slate-700 truncate" title="${label}">${label}</span>
                    `;
                    assetList.appendChild(div);
                }
            });
            
            // Wenn keine validen IDs gefunden wurden, Hinweis zeigen
            if (assetList.children.length === 0) {
                assetList.innerHTML = '<p class="text-xs text-slate-400 italic p-2">Keine verknüpfte Hardware mit gültiger ID gefunden.</p>';
            }
    
        } else {
            assetSection.classList.add('hidden');
        }
    
        openModal('triageModal'); 
    }

    function closeTriageModal() { 
        closeModal('triageModal'); 
    }    
    async function submitTriage() { 
        const order = maintenanceOrders.find(o => o.id === currentOrderId); 
        if(!order) return; 
    
        const priorityEl = document.querySelector('input[name="triagePriority"]:checked'); 
        if (!priorityEl) { alert("Bitte eine Priorität wählen."); return; } 
        
        // 1. Werte setzen
        order.priority = priorityEl.value; 
        order.isUnitBlocker = document.getElementById('triageUnitBlocker').checked;
        
        // Legacy Feld 'isBlocker' synchron halten für Abwärtskompatibilität
        // Ein Auftrag ist ein "Blocker", wenn Unit gesperrt ist ODER Hardware gesperrt ist.
        
        // 2. Hardware Status aktualisieren
        const checkboxes = document.querySelectorAll('.triage-asset-cb');
        const idsToBlock = [];
        const idsToFree = [];
        
        checkboxes.forEach(cb => {
            if (cb.checked) idsToBlock.push(cb.value);
            else idsToFree.push(cb.value);
        });
        
        order.blockedAssetIds = idsToBlock; // Speichern welche WIR blockieren
        order.isBlocker = order.isUnitBlocker || idsToBlock.length > 0;
    
        // API Calls für Hardware Status
        if (idsToBlock.length > 0) {
            await fetch('/api/hardware/set_status', {
                method: 'POST', headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({ ids: idsToBlock, status: 'Maintenance' })
            });
        }
        if (idsToFree.length > 0) {
            // Nur freigeben, wenn sie vorher blockiert waren (Status 'Maintenance')
            // Das übernimmt das Backend idealerweise oder wir setzen 'Available'.
            await fetch('/api/hardware/set_status', {
                method: 'POST', headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({ ids: idsToFree, status: 'Available' })
            });
        }
    
        order.logs.push({
            user: currentUser,
            timestamp: new Date().toLocaleString(),
            note: `Triage: ${order.priority}, Unit-Block: ${order.isUnitBlocker ? 'Ja' : 'Nein'}, HW-Block: ${idsToBlock.length} Teile.`
        });

        await saveOrder(order);
        renderOrdersTable();
        closeTriageModal();
        openWorkOrderModal(currentOrderId);
    }
    function createLinkedIssue() {
        document.getElementById('createIssueForm').reset();
        
        // 1. Definitionen ZUERST
        const pInput = document.getElementById('issuePlantIndex');
        const idInput = document.getElementById('issueFacilityUnitId');
        
        // --- FIX: parentOrder definieren, BEVOR wir es nutzen ---
        // Achtung: Wenn wir ein Ad-Hoc Issue machen, ist currentOrderId vielleicht null/undefined.
        // Das .find() gibt dann undefined zurück. Wir müssen also prüfen, ob parentOrder existiert.
        const parentOrder = currentOrderId ? maintenanceOrders.find(o => o.id === currentOrderId) : null;
        // ---------------------------------------------------------

        // 2. Dann benutzen
        if(pInput) pInput.value = '';
        if(idInput) idInput.value = '';
        
        const assetList = document.getElementById('issueAssetList');
        const assetContainer = document.getElementById('issueAssetContainer');
        assetList.innerHTML = '';
        assetContainer.classList.add('hidden');
        document.getElementById('issuePlantIndex').value = '';

        // NEU: Reset
        document.getElementById('issueFacilityUnitId').value = '';

        // NEU: Plant Index UND ID übernehmen (NUR WENN parentOrder existiert)
        if (parentOrder && parentOrder.plantIndex && pInput) {
            pInput.value = parentOrder.plantIndex;
            // ID übernehmen (falls Parent eine hat)
            if(parentOrder.facilityUnitId && idInput) {
                idInput.value = parentOrder.facilityUnitId;
            }
        }  
        
        let sourceAssets = []; 

    
        if (adHocContext) {
            // FALL A: AD-HOC (Vom Button "Issue melden")
            if (adHocContext.type === 'asset') {
                // adHocContext.value ist jetzt das Objekt {id, label} (dank unserem Fix)
                sourceAssets = [ adHocContext.value ];
            }
            // Wenn type 'unlinked' ist, bleibt die Liste leer (korrekt)
            
        } else if (parentOrder) {
            // FALL B: AUS AUFTRAG (Vom Button "Issue" im Auftrag)
            
            // Kategorie übernehmen
            const parentArea = parentOrder.topic || parentOrder.category;
            if (parentArea) document.getElementById('issueCategory').value = parentArea;
            if (parentOrder.plantIndex && pInput) {
                pInput.value = parentOrder.plantIndex;
            }
            
            // Assets aus dem Parent holen und normalisieren
            const rawAssets = parentOrder.maintenanceObjects || (parentOrder.maintenanceObject ? [parentOrder.maintenanceObject] : []);
            
            rawAssets.forEach(assetStr => {
                try {
                    // Ist es schon JSON? (Neues Format)
                    if (assetStr.trim().startsWith('{')) {
                        sourceAssets.push(JSON.parse(assetStr));
                    } else {
                        // Nein, alter String -> Versuche ID im globalen Inventar zu finden
                        const item = hardwareInventoryList.find(h => h.label === assetStr);
                        sourceAssets.push({ 
                            id: item ? item.id : null, 
                            label: assetStr 
                        });
                    }
                } catch(e) {
                     // Parsing Fehler -> Fallback auf String ohne ID
                     sourceAssets.push({ id: null, label: assetStr });
                }
            
            });
        }
    
        if (sourceAssets.length > 0) {
            assetContainer.classList.remove('hidden');
            sourceAssets.forEach(asset => {
                // WICHTIG: Das Value für die Checkbox muss der valide JSON-String sein!
                // Damit ist es kompatibel mit dem Speicher-Format der Datenbank.
                const valueString = JSON.stringify({ id: asset.id, label: asset.label });
                
                // Checkbox anhaken?
                let isChecked = '';
                
                // Fall 1: Wir kommen aus der Ad-Hoc Suche -> Das gefundene Element direkt anhaken
                if (adHocContext && adHocContext.type === 'asset') {
                    // Vergleich auf ID (wenn vorhanden) oder Label (Fallback)
                    if (asset.id && adHocContext.value.id && asset.id === adHocContext.value.id) {
                        isChecked = 'checked';
                    } else if (asset.label === adHocContext.value.label) {
                        isChecked = 'checked';
                    }
                }
                
                // (Fall 2: Aus Auftrag -> Hier haken wir standardmäßig nichts an, 
                //  der User soll wählen, was defekt ist. Oder man könnte 'checked' setzen, 
                //  wenn es nur ein Asset gibt. Das ist Geschmackssache. Aktuell: Leer lassen.)

                const div = document.createElement('div');
                div.className = "flex items-start gap-2 p-1 hover:bg-slate-50 rounded";
                // Wir nutzen single quotes '...' für das value Attribut, damit das JSON (double quotes) reinpasst
                div.innerHTML = `<input type="checkbox" value='${valueString}' ${isChecked} class="mt-1 w-4 h-4 text-yellow-600 border-gray-300 rounded focus:ring-yellow-500"><span class="text-sm text-slate-700 break-all">${asset.label}</span>`;
                assetList.appendChild(div);
            });
        }
        
        openModal('createIssueModal');
    }
    function closeCreateIssueModal(skipReturn = false) {
        // IMMER den Ad-hoc-Kontext zurücksetzen, wenn das Modal geschlossen wird
        adHocContext = null;
        
        closeModal('createIssueModal');
        
        document.getElementById('createIssueForm').reset();
        const btn = document.getElementById('btnSaveIssue');
        if(btn) {
            btn.onclick = submitCreateIssue;
            btn.innerHTML = '<span class="material-icons-outlined text-sm">save</span> Mangel anlegen';
        }
        document.getElementById('editIssueMetaContainer').classList.add('hidden');
        
        // Nur zum Parent zurückkehren, wenn es nicht explizit übersprungen wird UND ein Parent existiert
        if (!skipReturn && currentOrderId) {
            const exists = maintenanceOrders.find(o => o.id === currentOrderId);
            if(exists) openWorkOrderModal(currentOrderId);
        }
    }

    async function submitCreateIssue() {
        const title = document.getElementById('issueTitle').value.trim();
        if (!title) {
            alert("Titel ist Pflicht.");
            return;
        }

        const isAdHoc = adHocContext !== null;
        const sourceTaskId = isAdHoc ? null : currentOrderId;
        const pIndex = document.getElementById('issuePlantIndex').value.trim();
        const parentOrder = sourceTaskId ? maintenanceOrders.find(o => o.id === sourceTaskId) : null;
        
        const desc = document.getElementById('issueDescription').value.trim();
        const prio = "OPEN"; // Ad-hoc Issues starten immer als "OPEN" zur Bewertung
        const isBlocker = false;
        const cat = document.getElementById('issueCategory').value;
        const affectedAssets = Array.from(document.querySelectorAll('#issueAssetList input:checked')).map(cb => cb.value);
        const fileInput = document.getElementById('issueAttachment');
        let attachments = [];
        if (fileInput.files.length > 0) {
            const res = await handleUpload(fileInput.files[0]);
            if (res) attachments.push({ name: fileInput.files[0].name, dataUrl: res.path });
        }
        const newId = `ISS-${new Date().getFullYear()}-${String(Date.now()).slice(-4)}`;
        const reviewerId = ""; // Bleibt vorerst leer
    
        const logMessage = sourceTaskId 
            ? `Issue erstellt aus Auftrag ${sourceTaskId}. Status: Unbewertet.`
            : `Ad-hoc Issue erstellt. Status: Unbewertet.`;
    
        const newIssue = {
            id: newId,
            type: 'I',
            sourceTask: sourceTaskId, // Wird hier korrekt gesetzt (oder eben nicht)
            task: title,
            description: desc,
            priority: prio,
            category: cat,
            isBlocker: isBlocker,
            topic: cat,
            affectedAssets: affectedAssets,
            testbench: sourceTaskId ? (parentOrder?.testbench || '') : '',
            plantIndex: pIndex,
            facilityUnitId: document.getElementById('issueFacilityUnitId').value,
            assigned: '',
            date: new Date().toISOString().split('T')[0], // Fälligkeit ist heute
            status: 'OPEN',
            requiresReview: true,
            designatedReviewer: reviewerId,
            attachments: attachments,
            logs: [{ user: currentUser, timestamp: new Date().toLocaleString(), note: logMessage }]
        };
    
        maintenanceOrders.push(newIssue);
        await saveOrder(newIssue);
        renderOrdersTable();

        // Wichtig: Das Modal ohne Rückkehr zum Eltern-Auftrag schließen
        closeCreateIssueModal(true);

        // Optional: Direkt das neue Issue-Modal öffnen
        openWorkOrderModal(newId);

        // Wenn es einen Eltern-Auftrag gab, dort noch einen Log-Eintrag hinzufügen
        if (sourceTaskId) {
            const parent = maintenanceOrders.find(o => o.id === sourceTaskId);
            if(parent) {
                parent.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Issue ${newId} gemeldet.` });
                await saveOrder(parent);
            }
        }
    }
    function createLinkedAction() {
        document.getElementById('createActionForm').reset();
        const tomorrow = new Date();
        tomorrow.setDate(tomorrow.getDate() + 1);
        document.getElementById('actionDueDate').value = tomorrow.toISOString().split('T')[0];
        const pInput = document.getElementById('actionPlantIndex');
        if(pInput) pInput.value = '';
        const assetList = document.getElementById('actionAssetList');
        const assetContainer = document.getElementById('actionAssetContainer');
        assetList.innerHTML = '';
        assetContainer.classList.add('hidden');
        const parentIssue = maintenanceOrders.find(o => o.id === currentOrderId);
        document.getElementById('actionFacilityUnitId').value = '';

        if (parentIssue.plantIndex && pInput) {
            pInput.value = parentIssue.plantIndex;
            if(parentIssue.facilityUnitId) {
                document.getElementById('actionFacilityUnitId').value = parentIssue.facilityUnitId;
            }
        }

        
        if (parentIssue) {
            const parentArea = parentIssue.topic || parentIssue.category;
            if (parentArea) document.getElementById('actionCategory').value = parentArea;

            // NEU: Plant Index übernehmen
            if (parentIssue.plantIndex && pInput) {
                pInput.value = parentIssue.plantIndex;
            }

            // Logik zum Sammeln der Assets (kann komplex sein, da es von Issues kommt)
            let sourceAssets = []; 
            (parentIssue.affectedAssets || []).forEach(assetStr => {
                try {
                    if (assetStr.trim().startsWith('{')) {
                        sourceAssets.push(JSON.parse(assetStr));
                    } else {
                        // Fallback für alte Daten
                        const item = hardwareInventoryList.find(h => h.label === assetStr);
                        sourceAssets.push({ id: item ? item.id : null, label: assetStr });
                    }
                } catch(e) {
                    sourceAssets.push({ id: null, label: assetStr });
                }
            });

            if (sourceAssets.length > 0) {
                assetContainer.classList.remove('hidden');
                sourceAssets.forEach(asset => {
                    const valueString = JSON.stringify(asset);
                    const div = document.createElement('div');
                    div.className = "flex items-start gap-2 p-1 hover:bg-slate-50 rounded";
                    div.innerHTML = `<input type="checkbox" value='${valueString}' class="mt-1 w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500"><span class="text-sm text-slate-700 break-all">${asset.label}</span>`;
                    assetList.appendChild(div);
                });
            }
        }
        openModal('createActionModal');
    }
    function closeCreateActionModal(skipReturn = false) { 
        closeModal('createActionModal');
    
        document.getElementById('createActionForm').reset();
        const btn = document.getElementById('btnSaveAction');
        if(btn) {
            btn.onclick = submitCreateAction;
            btn.innerHTML = '<span class="material-icons-outlined text-sm">save</span> Speichern';
        }
        if (!skipReturn && currentOrderId) {
            const exists = maintenanceOrders.find(o => o.id === currentOrderId);
            if(exists) openWorkOrderModal(currentOrderId);
        }
    }    
async function submitCreateAction() { 
        const title = document.getElementById('actionTitle').value.trim(); 
        if (!title) { alert("Titel ist Pflicht."); return; } 
        
        const instructions = document.getElementById('actionInstructions').value.trim(); 
        const dueDate = document.getElementById('actionDueDate').value; 
        const effort = document.getElementById('actionEffort').value.trim(); 
        const spares = document.getElementById('actionSpareParts').value.trim(); 
        const cat = document.getElementById('actionCategory').value; 
        const affectedAssets = Array.from(document.querySelectorAll('#actionAssetList input:checked')).map(cb => cb.value);
        const parentIssue = maintenanceOrders.find(o => o.id === currentOrderId);

        const newId = `ACT-${new Date().getFullYear()}-${String(Date.now()).slice(-4)}`; 
        
        // 1. Priorität: Eingabe im Modal (falls geändert)
        let pIndex = document.getElementById('actionPlantIndex').value.trim();
        let fUnitId = document.getElementById('actionFacilityUnitId').value;
        let tBench = "";

        // 2. Fallback: Erbe vom Parent (wenn Input leer ist oder gleich Parent)
        if (parentIssue) {
            if (!pIndex) {
                pIndex = parentIssue.plantIndex || '';
                fUnitId = parentIssue.facilityUnitId || '';
            }
            tBench = parentIssue.testbench || '';
        }
        // -------------------------------
        
        const newAction = { 
            id: newId, 
            type: 'A', 
            linkedIssue: currentOrderId, 
            task: title, 
            description: instructions, 
            estimatedTime: effort, 
            spareParts: spares, 
            topic: cat, 
            affectedAssets: affectedAssets,
            testbench: tBench, // Sauberer Wert aus Parent
            plantIndex: pIndex,
            facilityUnitId: fUnitId,
            assigned: '',
            date: dueDate || new Date().toISOString().split('T')[0],
            status: isTestMechanic ? 'Proposed' : 'Pending',
            logs: [{ user: currentUser, timestamp: new Date().toLocaleString(), note: isTestMechanic ? `Maßnahme vorgeschlagen (wartet auf Freigabe).` : `Maßnahme für Issue ${currentOrderId} definiert.` }]
        }; 
        
        maintenanceOrders.push(newAction);
        await saveOrder(newAction);
        renderOrdersTable();
        closeCreateActionModal(true);

        if(parentIssue) {
            parentIssue.logs.push({ user: 'System', timestamp: new Date().toLocaleString(), note: `Abstellmaßnahme ${newId} angelegt.` });
            await saveOrder(parentIssue);
            renderAuditTrail(parentIssue);
            openWorkOrderModal(currentOrderId);
        }
    }
    function editCurrentOrder(type) { 
        const order = maintenanceOrders.find(o => o.id === currentOrderId); 
        if(!order) return; 
        
        // Prüfen, ob abgeschlossene Aufträge von Nicht-Admins bearbeitet werden
        if (order.status === 'Completed' && !currentUserIsAdmin) { 
            alert("Abgeschlossene Aufträge können nicht mehr bearbeitet werden."); 
            return; 
        } 
        
        closeWorkOrderModal(); 
        
        if (type === 'I') { 
            document.getElementById('issueTitle').value = order.task; 
            document.getElementById('issueDescription').value = order.description; 
            document.getElementById('issueCategory').value = order.category; 
            
            const editMetaContainer = document.getElementById('editIssueMetaContainer'); 
            if (order.priority === 'OPEN') { 
                editMetaContainer.classList.add('hidden'); 
            } else { 
                editMetaContainer.classList.remove('hidden'); 
                document.getElementById('issuePriority').value = order.priority; 
                document.getElementById('issueBlocker').checked = order.isBlocker; 
            }
    
            const assetList = document.getElementById('issueAssetList'); 
            const assetContainer = document.getElementById('issueAssetContainer'); 
            assetList.innerHTML = ''; 
            assetContainer.classList.add('hidden'); 
            const parentOrder = maintenanceOrders.find(p => p.id === order.sourceTask); 
            if (parentOrder) { 
                const assets = parentOrder.maintenanceObjects || (parentOrder.maintenanceObject ? [parentOrder.maintenanceObject] : []); 
                if (assets.length > 0) { 
                    assetContainer.classList.remove('hidden'); 
                    const savedAssets = order.affectedAssets || (order.affectedAsset ? [order.affectedAsset] : []); 
                    assets.forEach(asset => { 
                        const isChecked = savedAssets.includes(asset) ? 'checked' : ''; 
                        const div = document.createElement('div'); 
                        div.className = "flex items-start gap-2 p-1 hover:bg-slate-50 rounded"; 
                        div.innerHTML = `<input type="checkbox" value="${asset}" ${isChecked} class="mt-1 w-4 h-4 text-yellow-600 border-gray-300 rounded focus:ring-yellow-500"><span class="text-sm text-slate-700 break-all">${asset}</span>`; 
                        assetList.appendChild(div); 
                    }); 
                } 
            }
    
            const btn = document.getElementById('btnSaveIssue'); 
            btn.onclick = () => submitUpdateIssue(order.id); 
            btn.innerHTML = '<span class="material-icons-outlined text-sm">save</span> Änderungen speichern'; 
            
            openModal('createIssueModal'); 
    
        } else if (type === 'A') { 
            document.getElementById('actionTitle').value = order.task; 
            document.getElementById('actionInstructions').value = order.description; 
            document.getElementById('actionDueDate').value = order.date; 
            document.getElementById('actionEffort').value = order.estimatedTime || ''; 
            document.getElementById('actionSpareParts').value = order.spareParts || ''; 
            document.getElementById('actionCategory').value = order.topic || "Allgemein";
    
            const assetList = document.getElementById('actionAssetList'); 
            const assetContainer = document.getElementById('actionAssetContainer'); 
            assetList.innerHTML = ''; 
            assetContainer.classList.add('hidden'); 
            const parentIssue = maintenanceOrders.find(p => p.id === order.linkedIssue); 
            let sourceAssets = []; 
            if (parentIssue) { 
                if (parentIssue.affectedAssets && parentIssue.affectedAssets.length > 0) sourceAssets = parentIssue.affectedAssets; 
                else if (parentIssue.affectedAsset) sourceAssets = [parentIssue.affectedAsset]; 
                else if (parentIssue.maintenanceObjects && parentIssue.maintenanceObjects.length > 0) sourceAssets = parentIssue.maintenanceObjects; 
                else { 
                    const grandParent = maintenanceOrders.find(gp => gp.id === parentIssue.sourceTask); 
                    if (grandParent) sourceAssets = grandParent.maintenanceObjects || (grandParent.maintenanceObject ? [grandParent.maintenanceObject] : []); 
                } 
            } 
            if (sourceAssets.length > 0) { 
                assetContainer.classList.remove('hidden'); 
                const savedAssets = order.affectedAssets || []; 
                sourceAssets.forEach(asset => { 
                    const isChecked = savedAssets.includes(asset) ? 'checked' : ''; 
                    const div = document.createElement('div'); 
                    div.className = "flex items-start gap-2 p-1 hover:bg-slate-50 rounded"; 
                    div.innerHTML = `<input type="checkbox" value="${asset}" ${isChecked} class="mt-1 w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500"><span class="text-sm text-slate-700 break-all">${asset}</span>`; 
                    assetList.appendChild(div); 
                }); 
            }
    
            const btn = document.getElementById('btnSaveAction'); 
            btn.onclick = () => submitUpdateAction(order.id); 
            btn.innerHTML = '<span class="material-icons-outlined text-sm">save</span> Änderungen speichern'; 
            
            openModal('createActionModal'); 
    
        } else { 
            alert("Maintenance-Aufträge werden über die Vorlagen verwaltet."); 
        } 
    }

    async function submitUpdateIssue(id) { const order = maintenanceOrders.find(o => o.id === id); if(!order) return; order.task = document.getElementById('issueTitle').value.trim(); order.description = document.getElementById('issueDescription').value.trim(); order.category = document.getElementById('issueCategory').value; const editMetaContainer = document.getElementById('editIssueMetaContainer'); if (!editMetaContainer.classList.contains('hidden')) { order.priority = document.getElementById('issuePriority').value; order.isBlocker = document.getElementById('issueBlocker').checked; } order.topic = order.category; order.affectedAssets = Array.from(document.querySelectorAll('#issueAssetList input:checked')).map(cb => cb.value); delete order.affectedAsset; const fileInput = document.getElementById('issueAttachment'); if(fileInput.files.length > 0) { const res = await handleUpload(fileInput.files[0]); if(res) { if(!order.attachments) order.attachments = []; order.attachments.push({ name: fileInput.files[0].name, dataUrl: res.path }); } } order.logs.push({ user: currentUser, timestamp: new Date().toLocaleString(), note: `Auftrag bearbeitet.` }); await saveOrder(order); renderOrdersTable(); closeCreateIssueModal(true); openWorkOrderModal(id); }
    async function submitUpdateAction(id) { const order = maintenanceOrders.find(o => o.id === id); if(!order) return; order.task = document.getElementById('actionTitle').value.trim(); order.description = document.getElementById('actionInstructions').value.trim();const affectedAssets = Array.from(document.querySelectorAll('#actionAssetList input:checked')).map(cb => cb.value); order.date = document.getElementById('actionDueDate').value; order.estimatedTime = document.getElementById('actionEffort').value.trim(); order.spareParts = document.getElementById('actionSpareParts').value.trim(); order.topic = document.getElementById('actionCategory').value; order.affectedAssets = Array.from(document.querySelectorAll('#actionAssetList input:checked')).map(cb => cb.value); order.logs.push({ user: currentUser, timestamp: new Date().toLocaleString(), note: `Maßnahme bearbeitet.` }); await saveOrder(order); renderOrdersTable(); closeCreateActionModal(true); openWorkOrderModal(id); }

    function switchToDefView() { document.getElementById('defViewContainer').classList.remove('hidden'); document.getElementById('definitionForm').classList.add('hidden'); document.getElementById('defHeaderBadgeView').classList.remove('hidden'); document.getElementById('defHeaderToggleEdit').classList.add('hidden'); document.getElementById('btnSwitchToDefEdit').classList.remove('hidden'); document.getElementById('defModalTitle').textContent = "Wartungsvorlage (Ansicht)"; document.getElementById('defFooterView').classList.remove('hidden'); document.getElementById('defFooterEdit').classList.add('hidden'); }
    function switchToDefEditMode() { document.getElementById('defViewContainer').classList.add('hidden'); document.getElementById('definitionForm').classList.remove('hidden'); document.getElementById('defHeaderBadgeView').classList.add('hidden'); document.getElementById('defHeaderToggleEdit').classList.remove('hidden'); document.getElementById('btnSwitchToDefEdit').classList.add('hidden'); document.getElementById('defModalTitle').textContent = "Wartungsvorlage bearbeiten"; document.getElementById('defFooterView').classList.add('hidden'); document.getElementById('defFooterEdit').classList.remove('hidden'); const id = document.getElementById('defId').value; if(id) document.getElementById('btnDefDelete').classList.remove('invisible'); else document.getElementById('btnDefDelete').classList.add('invisible'); }
    function cancelDefEdit() { const id = document.getElementById('defId').value; if(id) { switchToDefView(); } else { closeDefinitionModal(); } }
    function deleteDefinitionFromModal() { const id = document.getElementById('defId').value; if(id) { deleteDefinition(id); closeDefinitionModal(); } }
    function openDefinitionModal(id = null) { 
        document.getElementById('definitionForm').reset(); 
        document.getElementById('defFacilityUnitId').value = ''; 
        document.getElementById('defImagePreview').innerHTML = ''; 
        document.getElementById('defDocPreview').innerHTML = ''; 
        document.getElementById('defChecklist').innerHTML = ''; 
        document.getElementById('defAssetContainer').innerHTML = ''; 
        document.getElementById('defConsumablesContainer').innerHTML = ''; 
        document.getElementById('defReqReview').checked = false; 
        document.getElementById('defReviewerBox').classList.add('hidden'); 
        document.getElementById('defReviewer').value = ''; 
        document.getElementById('defActive').checked = true; 
        document.getElementById('defIdView').textContent = ''; 
        document.getElementById('defAssetsView').innerHTML = ''; 
        document.getElementById('defConsumablesView').innerHTML = ''; 
        document.getElementById('defChecklistView').innerHTML = ''; 
        document.getElementById('defAttachmentsView').innerHTML = ''; 
        currentDefImages = []; currentDefDocs = []; currentChecklist = []; currentDefAssets = []; currentDefConsumables = []; currentDefTags = [];
        updateCompanyDropdown(); 
        if(id) { 
            const def = taskDefinitions.find(d => d.id === id);  
            document.getElementById('defId').value = def.id; 
            document.getElementById('defTask').value = def.task; 
            document.getElementById('defDesc').value = def.description || ''; 
            document.getElementById('defTopic').value = def.topic; 
            document.getElementById('defTestbench').value = def.testbench; 
            document.getElementById('defIndex').value = def.plantIndex || ''; 
            document.getElementById('defIntVal').value = def.intervalValue;
            document.getElementById('defIntUnit').value = def.intervalUnit;
            document.getElementById('defStartDate').value = def.startDate || '';
            document.getElementById('defIntervalType').value = def.intervalType || 'rolling';
            if (def.fixedWeekday !== undefined && def.fixedWeekday !== '') document.getElementById('defFixedWeekday').value = def.fixedWeekday;
            if (def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '') { document.getElementById('defFixedDayOfMonth').value = def.fixedDayOfMonth; document.getElementById('defFixedDay').value = def.fixedDayOfMonth; }
            if (def.fixedMonth !== undefined && def.fixedMonth !== '') document.getElementById('defFixedMonth').value = def.fixedMonth;
            updateIntervalTypeUI();
            document.getElementById('defActive').checked = def.active !== false; 
            document.getElementById('defDuration').value = def.duration || ''; 
            if(def.externalCompanyId) document.getElementById('defExternalCompany').value = def.externalCompanyId; 
            if (def.requiresReview) { 
                document.getElementById('defReqReview').checked = true; 
                document.getElementById('defReviewerBox').classList.remove('hidden'); 
                document.getElementById('defReviewer').value = def.designatedReviewer || ''; 
            } 
            if(def.images) currentDefImages = [...def.images]; 
            if(def.documents) currentDefDocs = [...def.documents]; 
            if(def.checklist) currentChecklist = [...def.checklist]; 
            if(def.maintenanceObjects) currentDefAssets = [...def.maintenanceObjects]; 
            else if(def.maintenanceObject) currentDefAssets.push(def.maintenanceObject); 
            if(def.consumablesList) currentDefConsumables = [...def.consumablesList];
            else if(def.consumables) currentDefConsumables = def.consumables.split(',').map(s=>s.trim()).filter(s=>s);
            currentDefTags = def.tags ? [...def.tags] : [];
            renderDefAttachments();
            renderChecklist();
            renderAssets();
            renderConsumables();
            renderTags();
            document.getElementById('defIdView').textContent = def.id; 
            document.getElementById('defTaskView').textContent = def.task; 
            document.getElementById('defTopicView').textContent = def.topic; 
            const nextPendingOrder = maintenanceOrders.find(o => o.parentTaskId === def.id && (o.status === 'Pending' || o.status === 'In Progress'));
            let nextDueDisplay = '-';
            if (nextPendingOrder) {
                try { nextDueDisplay = new Date(nextPendingOrder.date).toLocaleDateString('de-DE'); } catch(e) { nextDueDisplay = nextPendingOrder.date; }
                if (nextPendingOrder.status === 'In Progress') nextDueDisplay += ' (in Bearbeitung)';
            } else if (def.active === false) {
                nextDueDisplay = 'Inaktiv';
            }
            document.getElementById('defStartDateView').textContent = nextDueDisplay;
            const rhythmBadge = def.intervalType === 'fixed' ? '<span class="ml-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-blue-50 text-blue-700 border border-blue-200">Fester Rhythmus</span>' : '<span class="ml-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-slate-100 text-slate-500 border border-slate-200">Rollierend</span>';
            document.getElementById('defIntervalView').innerHTML = `${def.intervalValue} ${def.intervalUnit} ${rhythmBadge}`;
            const fixedSection = document.getElementById('defFixedRhythmSection');
            if (def.intervalType === 'fixed') {
                fixedSection.classList.remove('hidden');
                document.getElementById('defFixedRhythmView').textContent = getFixedRhythmDescription(def);
            } else {
                fixedSection.classList.add('hidden');
            } 
            document.getElementById('defDurationView').textContent = def.duration || '-'; 
            let compName = "Intern / Internes Team"; 
            if(def.externalCompanyId) { const c = companies.find(x => x.id === def.externalCompanyId); if(c) compName = c.name + " (Extern)"; } 
            document.getElementById('defExternalCompanyView').textContent = compName; 
            let revTxt = "Nicht erforderlich"; 
            if(def.requiresReview) { revTxt = "Erforderlich"; if(def.designatedReviewer) revTxt += ` (durch ${def.designatedReviewer})`; } 
            document.getElementById('defReviewView').textContent = revTxt; 
            document.getElementById('defTestbenchView').textContent = def.testbench || '-'; 
            document.getElementById('defIndexView').textContent = def.plantIndex || '-';
            document.getElementById('defFacilityUnitId').value = def.facilityUnitId || ''; 
            const assetsDiv = document.getElementById('defAssetsView'); 
            if(currentDefAssets.length > 0) { 
                currentDefAssets.forEach(a => {
                    let lbl = a;
                    try { if(a.startsWith('{')) lbl = JSON.parse(a).label; } catch(e){}
                    assetsDiv.innerHTML += `<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 text-blue-700 border border-blue-100">${lbl}</span>`; 
                }); 
            }
            else assetsDiv.innerHTML = '<span class="text-xs text-slate-400 italic">-</span>'; 
            const consDiv = document.getElementById('defConsumablesView');
            if(currentDefConsumables.length > 0) { currentDefConsumables.forEach(c => consDiv.innerHTML += `<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-slate-100 text-slate-600 border border-slate-200">${c}</span>`); }
            else consDiv.innerHTML = '<span class="text-xs text-slate-400 italic">-</span>';
            const tagsViewDiv = document.getElementById('defTagsView');
            if(currentDefTags.length > 0) { tagsViewDiv.innerHTML = currentDefTags.map(t => `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-violet-50 text-violet-700 border border-violet-200">${t.startsWith('#') ? t : '#'+t}</span>`).join(''); }
            else tagsViewDiv.innerHTML = '<span class="text-xs text-slate-400 italic">-</span>';
            const clUl = document.getElementById('defChecklistView');
            if(currentChecklist.length > 0) { currentChecklist.forEach(item => { const vmatch = item.match(/\{value(?::([^}]*))?\}/); const hasV = !!vmatch; const vUnit = hasV && vmatch[1] ? vmatch[1].trim() : ''; const lbl = item.replace(/\{value(?::[^}]*)?\}/, '').trim(); const badge = hasV ? `<span class="ml-1 inline-flex items-center px-1 py-0 rounded text-[9px] font-bold bg-blue-50 text-blue-500 border border-blue-100"><span class="material-icons-outlined text-[9px]">speed</span>${vUnit ? ' '+vUnit : ''}</span>` : ''; clUl.innerHTML += `<li class="flex items-center gap-1 text-xs text-slate-600"><span class="material-icons-outlined text-[10px] flex-shrink-0 text-slate-400">check_box_outline_blank</span><span>${lbl}</span>${badge}</li>`; }); }
            else clUl.innerHTML = '<li class="text-xs text-slate-400 italic">Keine Checkliste.</li>';
            document.getElementById('defDescView').textContent = def.description || 'Keine Beschreibung.'; 
            const attachDiv = document.getElementById('defAttachmentsView'); 
            const allAttach = [...currentDefImages, ...currentDefDocs]; 
            if(allAttach.length > 0) { allAttach.forEach(a => { let icon = 'description'; if(a.dataUrl && a.dataUrl.startsWith('data:image')) icon = 'image'; attachDiv.innerHTML += `<div class="flex items-center gap-2 p-2 bg-slate-50 border rounded text-xs text-slate-600"><span class="material-icons-outlined text-sm">${icon}</span><span class="truncate flex-1">${a.name}</span></div>`; }); } 
            else attachDiv.innerHTML = '<span class="text-xs text-slate-400 italic col-span-2">Keine Anhänge.</span>'; 
            const badge = document.getElementById('defHeaderBadgeView'); 
            if(def.active !== false) { badge.className = "px-2 py-0.5 rounded text-xs font-bold bg-green-100 text-green-800 border border-green-200"; badge.textContent = "AKTIV"; } 
            else { badge.className = "px-2 py-0.5 rounded text-xs font-bold bg-slate-100 text-slate-500 border border-slate-200"; badge.textContent = "INAKTIV"; } 
            switchToDefView(); 
        } else { 
            document.getElementById('defId').value = '';
            document.getElementById('defStartDate').value = new Date().toISOString().split('T')[0];
            document.getElementById('defIntervalType').value = 'rolling';
            document.getElementById('defFixedWeekday').value = '1';
            document.getElementById('defFixedDayOfMonth').value = '1';
            document.getElementById('defFixedDay').value = '1';
            document.getElementById('defFixedMonth').value = '1';
            updateIntervalTypeUI();
            switchToDefEditMode();
            document.getElementById('btnDefDelete').classList.add('invisible'); 
        } 
        const el = document.getElementById('defChecklist');
        if(sortableChecklist) { try { sortableChecklist.destroy(); } catch(e){} }
        if (typeof Sortable !== 'undefined') {
            sortableChecklist = Sortable.create(el, { handle: '.drag-handle', animation: 150, onEnd: function (evt) { const item = currentChecklist.splice(evt.oldIndex, 1)[0]; currentChecklist.splice(evt.newIndex, 0, item); }, });
        }
        
        // HIER IST DIE KORREKTUR
        openModal('definitionModal'); 
    }
    function closeDefinitionModal() { 
        closeModal('definitionModal'); 
    }    
    function toggleReviewerInput() { const cb = document.getElementById('defReqReview'); const box = document.getElementById('defReviewerBox'); if(cb.checked) box.classList.remove('hidden'); else box.classList.add('hidden'); }
    function toggleNewItemMeasure() {
        const unitInput = document.getElementById('newChecklistUnit');
        const btn       = document.getElementById('btnToggleMeasure');
        const isActive  = !unitInput.classList.contains('hidden');
        if (isActive) {
            unitInput.classList.add('hidden');
            unitInput.value = '';
            btn.classList.remove('bg-blue-50', 'border-blue-400', 'text-blue-600');
            btn.classList.add('text-slate-500', 'border-slate-300');
        } else {
            unitInput.classList.remove('hidden');
            btn.classList.add('bg-blue-50', 'border-blue-400', 'text-blue-600');
            btn.classList.remove('text-slate-500', 'border-slate-300');
            unitInput.focus();
        }
    }
    function addChecklistItem() {
        const labelInput = document.getElementById('newChecklistItem');
        const unitInput  = document.getElementById('newChecklistUnit');
        const label = labelInput.value.trim();
        if (!label) return;
        const unitRaw = unitInput.classList.contains('hidden') ? null : unitInput.value.trim();
        let itemStr;
        if (unitRaw === null) {
            itemStr = label;
        } else if (unitRaw === '') {
            itemStr = label + ' {value}';
        } else {
            itemStr = label + ' {value:' + unitRaw + '}';
        }
        currentChecklist.push(itemStr);
        labelInput.value = '';
        unitInput.value = '';
        unitInput.classList.add('hidden');
        const btn = document.getElementById('btnToggleMeasure');
        btn.classList.remove('bg-blue-50', 'border-blue-400', 'text-blue-600');
        btn.classList.add('text-slate-500', 'border-slate-300');
        renderChecklist();
        labelInput.focus();
    }
    function removeChecklistItem(idx) { currentChecklist.splice(idx, 1); renderChecklist(); }
    function updateChecklistItem(idx, newValue) { currentChecklist[idx] = newValue.trim(); }
    function rebuildChecklistItem(idx, label, unit) {
        label = (label || '').trim();
        if (unit === null) {
            currentChecklist[idx] = label;
        } else if (unit === '') {
            currentChecklist[idx] = label ? label + ' {value}' : '{value}';
        } else {
            currentChecklist[idx] = label ? label + ' {value:' + unit + '}' : '{value:' + unit + '}';
        }
    }
    function getLabelForItem(idx) {
        const li = document.getElementById('defChecklist').children[idx];
        if (!li) return '';
        const inp = li.querySelectorAll('input[type="text"]')[0];
        return inp ? inp.value : '';
    }
    function getUnitForItem(idx) {
        const li = document.getElementById('defChecklist').children[idx];
        if (!li) return null;
        const inputs = li.querySelectorAll('input[type="text"]');
        if (inputs.length < 2) return null;
        return inputs[1].value;
    }
    function addValueMarkerToItem(idx) {
        const label = currentChecklist[idx].trim();
        currentChecklist[idx] = label + ' {value}';
        renderChecklist();
        const li = document.getElementById('defChecklist').children[idx];
        if (li) { const inputs = li.querySelectorAll('input[type="text"]'); if (inputs[1]) inputs[1].focus(); }
    }
    function removeValueMarker(idx) {
        currentChecklist[idx] = (currentChecklist[idx] || '').replace(/\s*\{value(?::[^}]*)?\}/, '').trim();
        renderChecklist();
    }
    function renderChecklist() {
        const ul = document.getElementById('defChecklist');
        ul.innerHTML = '';
        currentChecklist.forEach((item, idx) => {
            const valueMatch = item.match(/\{value(?::([^}]*))?\}/);
            const hasValue   = !!valueMatch;
            const unit       = hasValue && valueMatch[1] ? valueMatch[1].trim() : '';
            const labelText  = item.replace(/\{value(?::[^}]*)?\}/, '').trim();
            const li = document.createElement('li');
            li.className = "flex items-center gap-2 bg-white p-1 rounded border-b border-slate-100 last:border-0 group";
            const handle = document.createElement('span');
            handle.className = "material-icons-outlined text-base text-slate-300 cursor-grab drag-handle group-hover:text-slate-500 flex-shrink-0";
            handle.textContent = "drag_indicator";
            const labelInput = document.createElement('input');
            labelInput.type        = "text";
            labelInput.value       = labelText;
            labelInput.className   = "flex-grow text-xs text-slate-700 bg-transparent border-none p-1 focus:bg-slate-50 focus:ring-1 focus:ring-blue-200 rounded min-w-0";
            labelInput.placeholder = "Schritt beschreiben...";
            labelInput.oninput     = (e) => rebuildChecklistItem(idx, e.target.value, getUnitForItem(idx));
            li.appendChild(handle);
            li.appendChild(labelInput);
            if (hasValue) {
                const badge = document.createElement('span');
                badge.className = "flex-shrink-0 inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-bold bg-blue-50 text-blue-600 border border-blue-200 whitespace-nowrap";
                badge.innerHTML = '<span class="material-icons-outlined text-[10px]">speed</span> Messwert';
                const unitInput = document.createElement('input');
                unitInput.type        = "text";
                unitInput.value       = unit;
                unitInput.className   = "w-16 text-xs border border-slate-300 rounded px-1.5 py-0.5 text-center flex-shrink-0 focus:ring-1 focus:ring-blue-300";
                unitInput.placeholder = "Einheit";
                unitInput.maxLength   = 10;
                unitInput.title       = "Einheit (leer = dimensionslos)";
                unitInput.oninput     = (e) => rebuildChecklistItem(idx, getLabelForItem(idx), e.target.value);
                const removeValBtn = document.createElement('button');
                removeValBtn.type      = "button";
                removeValBtn.className = "text-blue-300 hover:text-red-500 transition-colors p-0.5 flex-shrink-0";
                removeValBtn.title     = "Messwert-Marker entfernen";
                removeValBtn.innerHTML = '<span class="material-icons-outlined text-sm">speed</span>';
                removeValBtn.onclick   = () => removeValueMarker(idx);
                li.appendChild(badge);
                li.appendChild(unitInput);
                li.appendChild(removeValBtn);
            } else {
                const addValBtn = document.createElement('button');
                addValBtn.type      = "button";
                addValBtn.className = "text-slate-300 hover:text-blue-500 transition-colors p-0.5 flex-shrink-0 opacity-0 group-hover:opacity-100";
                addValBtn.title     = "Messwert hinzufügen";
                addValBtn.innerHTML = '<span class="material-icons-outlined text-sm">speed</span>';
                addValBtn.onclick   = () => addValueMarkerToItem(idx);
                li.appendChild(addValBtn);
            }
            const delBtn = document.createElement('button');
            delBtn.type      = "button";
            delBtn.className = "text-slate-300 hover:text-red-500 transition-colors p-1 flex-shrink-0";
            delBtn.innerHTML = '<span class="material-icons-outlined text-sm">close</span>';
            delBtn.onclick   = () => removeChecklistItem(idx);
            li.appendChild(delBtn);
            ul.appendChild(li);
        });
    }

    function addAsset() { 
        const input = document.getElementById('defAssetInput'); 
        const val = input.value.trim(); 
        if(!val) return;

        // Versuche, das Item in der Liste zu finden
        const foundItem = hardwareInventoryList.find(i => i.label === val);

        let entryToSave;
        if (foundItem) {
            // Treffer! Wir speichern ein JSON-Objekt als String
            entryToSave = JSON.stringify({ id: foundItem.id, label: foundItem.label });
        } else {
            // Kein Treffer (Freitext oder Altdaten-Format). Wir speichern es so wie es ist.
            // Das erhält die Abwärtskompatibilität!
            entryToSave = val;
        }

        if(!currentDefAssets.includes(entryToSave)) { 
            currentDefAssets.push(entryToSave); 
            input.value = ''; 
            renderAssets(); 
        } 
    }    
    function removeAsset(val) { 
        // Wir filtern einfach den Array. Da 'val' der exakte String (ggf. JSON) ist, klappt das.
        currentDefAssets = currentDefAssets.filter(a => a !== val); 
        renderAssets(); 
    }

    function renderAssets() {
        const container = document.getElementById('defAssetContainer');
        container.innerHTML = ''; // Leere den Container wie bisher
    
        currentDefAssets.forEach(dataValue => {
            let displayText = dataValue;
    
            // Versuch, JSON zu parsen, um den schönen Namen zu erhalten
            try {
                if (dataValue.startsWith('{')) {
                    const obj = JSON.parse(dataValue);
                    displayText = obj.label;
                }
            } catch (e) {
                // Wenn das Parsen fehlschlägt, zeige den Roh-String an
                console.warn("Konnte Asset-String nicht als JSON parsen:", dataValue);
            }
    
            // --- NEU: Elemente programmatisch erstellen ---
    
            // 1. Das äußere 'tag-chip' Span-Element erstellen
            const tagChip = document.createElement('span');
            tagChip.className = 'tag-chip';
            tagChip.title = displayText;
            tagChip.textContent = displayText; // Fügt den Namen hinzu
    
            // 2. Den 'remove' Button (das 'x') erstellen
            const removeButton = document.createElement('span');
            removeButton.className = 'tag-chip-remove';
            removeButton.innerHTML = '×'; // Das 'x'-Symbol
            
            // 3. Den Event-Listener programmatisch hinzufügen
            // Hier wird 'dataValue' direkt als Variable übergeben, ohne String-Probleme!
            removeButton.addEventListener('click', () => {
                removeAsset(dataValue);
            });
    
            // 4. Den Button zum Tag hinzufügen
            tagChip.appendChild(removeButton);
    
            // 5. Das komplette Tag zum Container hinzufügen
            container.appendChild(tagChip);
        });
    }
    function addConsumable() { const input = document.getElementById('defConsumableInput'); const val = input.value.trim(); if(val && !currentDefConsumables.includes(val)) { currentDefConsumables.push(val); input.value = ''; renderConsumables(); } }
    function removeConsumable(val) { currentDefConsumables = currentDefConsumables.filter(a => a !== val); renderConsumables(); }
    function addTag() {
        const input = document.getElementById('defTagInput');
        const raw = input.value.trim().replace(/,$/, '');
        if (!raw) return;
        const tag = (raw.startsWith('#') ? raw : '#' + raw).toLowerCase().replace(/\s+/g, '_');
        if (!currentDefTags.includes(tag)) { currentDefTags.push(tag); renderTags(); }
        input.value = '';
    }
    function removeTag(tag) { currentDefTags = currentDefTags.filter(t => t !== tag); renderTags(); }
    function renderTags() {
        const container = document.getElementById('defTagContainer');
        if (!container) return;
        container.innerHTML = currentDefTags.map(t => `<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-violet-50 text-violet-700 border border-violet-200">${t}<span class="cursor-pointer hover:text-red-500 font-bold leading-none" onclick="removeTag('${t.replace(/'/g, "\\'")}')">×</span></span>`).join('');
    }
    function renderConsumables() { const c = document.getElementById('defConsumablesContainer'); c.innerHTML = ''; currentDefConsumables.forEach(val => { c.innerHTML += `<span class="tag-chip bg-gray-100 text-gray-700 border-gray-200">${val}<span class="tag-chip-remove text-gray-500 hover:text-gray-700" onclick="removeConsumable('${val}')">×</span></span>`; }); }
    async function handleUpload(file) { const formData = new FormData(); formData.append('file', file); try { const res = await fetch('/api/maintenance/upload', { method: 'POST', body: formData }); if(!res.ok) throw new Error(); return await res.json(); } catch(e) { alert('Upload fehlgeschlagen'); return null; } }
    document.getElementById('defImageInput').addEventListener('change', async (e) => { for(let file of e.target.files) { const result = await handleUpload(file); if(result) { currentDefImages.push({ name: file.name, dataUrl: result.path }); } } renderDefAttachments(); });
    document.getElementById('defDocInput').addEventListener('change', async (e) => { for(let file of e.target.files) { const result = await handleUpload(file); if(result) { currentDefDocs.push({ name: file.name, dataUrl: result.path }); } } renderDefAttachments(); });
    function renderDefAttachments() { 
        const iCon = document.getElementById('defImagePreview'); 
        const dCon = document.getElementById('defDocPreview'); 
        iCon.innerHTML = ''; 
        dCon.innerHTML = ''; 
    
        // Bilder rendern (mit Lightbox)
        currentDefImages.forEach((img, idx) => { 
            iCon.innerHTML += `
                <div class="flex items-center gap-2 bg-slate-50 p-1 rounded border border-slate-200 text-xs group hover:border-blue-300 transition-colors">
                    <!-- Bildvorschau: Klick öffnet Lightbox -->
                    <img src="${img.dataUrl}" class="w-8 h-8 object-cover rounded cursor-zoom-in hover:opacity-80 transition-opacity" 
                         onclick="event.stopPropagation(); openLightbox('${img.dataUrl}', '${img.name}')">
                    
                    <!-- Dateiname: Klick öffnet ebenfalls Lightbox -->
                    <span class="truncate flex-1 cursor-pointer hover:text-blue-600" 
                          onclick="openLightbox('${img.dataUrl}', '${img.name}')">${img.name}</span>
                    
                    <!-- Löschen Button -->
                    <span class="text-red-400 cursor-pointer hover:text-red-600 font-bold px-2"
                          onclick="(function(i){const removed=currentDefImages.splice(i,1)[0]; if(removed&&removed.dataUrl){const fname=removed.dataUrl.split('/').pop(); fetch('/api/maintenance/upload/'+encodeURIComponent(fname),{method:'DELETE'}).catch(e=>console.warn('Datei-Löschen fehlgeschlagen',e));} renderDefAttachments();})(${idx})">×</span>
                </div>`; 
        }); 
    
        // Dokumente rendern (ohne Lightbox, nur Icon)
        currentDefDocs.forEach((doc, idx) => { 
            dCon.innerHTML += `
                <div class="flex items-center gap-2 bg-slate-50 p-1 rounded border border-slate-200 text-xs hover:border-blue-300 transition-colors">
                    <span class="material-icons-outlined text-sm text-slate-400">description</span>
                    <span class="truncate flex-1">${doc.name}</span>
                    <span class="text-red-400 cursor-pointer hover:text-red-600 font-bold px-2"
                          onclick="(function(i){const removed=currentDefDocs.splice(i,1)[0]; if(removed&&removed.dataUrl){const fname=removed.dataUrl.split('/').pop(); fetch('/api/maintenance/upload/'+encodeURIComponent(fname),{method:'DELETE'}).catch(e=>console.warn('Datei-Löschen fehlgeschlagen',e));} renderDefAttachments();})(${idx})">×</span>
                </div>`; 
        }); 
    }    
function saveDefinition() {
        const id = document.getElementById('defId').value;
        const startDateVal = document.getElementById('defStartDate').value;
        if (!startDateVal) { alert("Bitte ein Startdatum wählen."); return; }
        
        const reqRev = document.getElementById('defReqReview').checked;
        const revUser = document.getElementById('defReviewer').value.trim();
        const isActive = document.getElementById('defActive').checked;
        const extComp = document.getElementById('defExternalCompany').value;
        
        const data = { 
            task: document.getElementById('defTask').value, 
            description: document.getElementById('defDesc').value, 
            topic: document.getElementById('defTopic').value, 
            testbench: document.getElementById('defTestbench').value, 
            plantIndex: document.getElementById('defIndex').value, 
            facilityUnitId: document.getElementById('defFacilityUnitId').value, 
            maintenanceObjects: currentDefAssets, 
            consumablesList: currentDefConsumables, 
            intervalValue: document.getElementById('defIntVal').value,
            intervalUnit: document.getElementById('defIntUnit').value,
            intervalType: document.getElementById('defIntervalType').value,
            fixedWeekday: document.getElementById('defFixedWeekday').value,
            fixedDayOfMonth: document.getElementById('defIntUnit').value === 'Monate' ? document.getElementById('defFixedDayOfMonth').value : document.getElementById('defFixedDay').value,
            fixedMonth: document.getElementById('defFixedMonth').value,
            duration: document.getElementById('defDuration').value,
            checklist: currentChecklist,
            startDate: startDateVal, 
            requiresReview: reqRev,
            designatedReviewer: revUser,
            active: isActive,
            externalCompanyId: extComp,
            images: currentDefImages,
            documents: currentDefDocs,
            tags: currentDefTags
        };
        
        const existingDef = id ? taskDefinitions.find(d => d.id === id) : null;
        
        const wasInactive = existingDef && existingDef.active === false;
        const isNew = !existingDef;

        // Inaktiv-Logik (Löschen von zukünftigen Aufträgen)
        if (!isActive && id) { 
             const ordersToDelete = maintenanceOrders.filter(o => { 
                 if (o.parentTaskId !== id) return false; 
                 if (o.status !== 'Pending') return false; 
                 const hasChildren = maintenanceOrders.some(child => child.sourceTask === o.id || child.linkedIssue === o.id); 
                 return !hasChildren; 
             }); 
             
             if (ordersToDelete.length > 0) {
                 if(confirm(`Wartungsplan inaktiv gesetzt. ${ordersToDelete.length} offene (Pending) Aufträge ohne Verknüpfungen werden gelöscht. Fortfahren?`)) {
                     const idsToDelete = ordersToDelete.map(o => o.id);
                     maintenanceOrders = maintenanceOrders.filter(o => !idsToDelete.includes(o.id));
                     deleteOrders(idsToDelete);
                     renderOrdersTable();
                 } else {
                     document.getElementById('defActive').checked = true; 
                     return; 
                 } 
             } 
        }

        // Speichern / Update im Array
        let defToSave;
        if(existingDef) {
            const idx = taskDefinitions.indexOf(existingDef);
            taskDefinitions[idx] = { ...taskDefinitions[idx], ...data };
            defToSave = taskDefinitions[idx];

            // --- NEU: SYNC DOWN zu Pending Aufträgen ---
            // Wir aktualisieren alle noch nicht begonnenen Aufträge mit den neuen Stammdaten
            const updatedOrders = [];
            maintenanceOrders.forEach(order => {
                if (order.parentTaskId === id && order.status === 'Pending') {
                    order.task = data.task;
                    order.description = data.description;
                    order.topic = data.topic;
                    order.testbench = data.testbench;
                    order.plantIndex = data.plantIndex;
                    order.facilityUnitId = data.facilityUnitId;
                    order.maintenanceObjects = [...data.maintenanceObjects]; // Kopie!
                    order.consumablesList = [...data.consumablesList];     // Kopie!
                    order.requiresReview = data.requiresReview;
                    order.designatedReviewer = data.designatedReviewer;
                    order.externalCompanyId = data.externalCompanyId;
                    // Checkliste nur updaten, wenn noch nichts abgehakt wurde (einfacher Check)
                    if (!order.checklistState || Object.keys(order.checklistState).length === 0) {
                        order.checklist = [...data.checklist];
                    }
                    updatedOrders.push(order);
                }
            });
            if (updatedOrders.length > 0) {
                console.log(`Info: ${updatedOrders.length} offene Aufträge wurden aktualisiert.`);
                saveOrdersBatch(updatedOrders);
            }
            // -------------------------------------------

        } else {
            const now = Date.now();
            const year = new Date().getFullYear();
            const uniqueSuffix = String(now).slice(-5);
            const newId = `MT-${year}-${uniqueSuffix}`;
            taskDefinitions.push({ id: newId, ...data });
            defToSave = taskDefinitions[taskDefinitions.length - 1];
        }

        saveDefinitionItem(defToSave);
        
        // Trigger Logik (Neu erstellen)
        if ((isNew && isActive) || (!isNew && wasInactive && isActive)) {
            let nextDate;
            if (data.intervalType === 'fixed') {
                nextDate = findNextFixedOccurrence(data);
            } else {
                nextDate = startDateVal;
                if (new Date(startDateVal) < new Date()) {
                    nextDate = calculateNextValidDateFromStart(startDateVal, data.intervalValue, data.intervalUnit);
                }
            } 
            
            const targetId = existingDef ? existingDef.id : taskDefinitions[taskDefinitions.length - 1].id; 
            createOrderFromDef(targetId, nextDate, true); 
            
            const msg = isNew ? "Vorlage erstellt" : "Wartungsplan reaktiviert"; 
            alert(`${msg} und Auftrag für ${nextDate} geprüft/generiert.`); 
        }
        
        renderDefinitionsTable(); 
        renderOrdersTable(); 
        closeDefinitionModal();
    }
    function updateIntervalTypeUI() {
        const type = document.getElementById('defIntervalType').value;
        const unit = document.getElementById('defIntUnit').value;
        const container = document.getElementById('defFixedRhythmContainer');
        if (type === 'fixed') {
            container.classList.remove('hidden');
            document.getElementById('defFixedWeekdayRow').classList.toggle('hidden', unit !== 'Wochen');
            document.getElementById('defFixedDayOfMonthRow').classList.toggle('hidden', unit !== 'Monate');
            document.getElementById('defFixedYearRow').classList.toggle('hidden', unit !== 'Jahre');
        } else {
            container.classList.add('hidden');
        }
    }
    function getFixedRhythmDescription(def) {
        const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
        const months = ['', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
        if (def.intervalUnit === 'Wochen' && def.fixedWeekday !== undefined && def.fixedWeekday !== '') {
            return `Jeden ${weekdays[parseInt(def.fixedWeekday)]}`;
        } else if (def.intervalUnit === 'Monate' && def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '') {
            return `Am ${def.fixedDayOfMonth}. jeden Monats`;
        } else if (def.intervalUnit === 'Jahre' && def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '' && def.fixedMonth !== undefined) {
            return `Am ${def.fixedDayOfMonth}. ${months[parseInt(def.fixedMonth)] || ''}`;
        }
        return '-';
    }
    function localDateStr(d) {
        return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
    }
    function findNextFixedOccurrence(def) {
        const now = new Date();
        const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        // Untergrenze: nie vor heute, nie vor startDate
        let lb = today;
        if (def.startDate) {
            const sd = new Date(def.startDate + 'T00:00:00');
            const startDay = new Date(sd.getFullYear(), sd.getMonth(), sd.getDate());
            if (startDay > lb) lb = startDay;
        }
        if (def.intervalUnit === 'Wochen' && def.fixedWeekday !== undefined && def.fixedWeekday !== '') {
            const targetWeekday = parseInt(def.fixedWeekday);
            const d = new Date(lb.getFullYear(), lb.getMonth(), lb.getDate());
            while (d.getDay() !== targetWeekday) d.setDate(d.getDate() + 1);
            return localDateStr(d);
        } else if (def.intervalUnit === 'Monate' && def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '') {
            const targetDay = parseInt(def.fixedDayOfMonth);
            let d = new Date(lb.getFullYear(), lb.getMonth(), targetDay);
            if (d < lb) d = new Date(lb.getFullYear(), lb.getMonth() + 1, targetDay);
            return localDateStr(d);
        } else if (def.intervalUnit === 'Jahre' && def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '' && def.fixedMonth !== undefined) {
            const targetDay = parseInt(def.fixedDayOfMonth);
            const targetMonth = parseInt(def.fixedMonth) - 1;
            let d = new Date(lb.getFullYear(), targetMonth, targetDay);
            if (d < lb) d = new Date(lb.getFullYear() + 1, targetMonth, targetDay);
            return localDateStr(d);
        }
        return localDateStr(lb);
    }
    function calculateNextFixedRhythmDate(def, referenceDateStr) {
        const parts = referenceDateStr.split('-').map(Number);
        const ref = new Date(parts[0], parts[1] - 1, parts[2]);
        const v = parseInt(def.intervalValue);
        if (def.intervalUnit === 'Wochen' && def.fixedWeekday !== undefined && def.fixedWeekday !== '') {
            const targetWeekday = parseInt(def.fixedWeekday);
            const d = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate() + v * 7);
            const daysUntil = (targetWeekday - d.getDay() + 7) % 7;
            d.setDate(d.getDate() + daysUntil);
            if (d <= ref) d.setDate(d.getDate() + 7);
            return localDateStr(d);
        } else if (def.intervalUnit === 'Monate' && def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '') {
            const targetDay = parseInt(def.fixedDayOfMonth);
            let d = new Date(ref.getFullYear(), ref.getMonth() + v, targetDay);
            if (d <= ref) d = new Date(ref.getFullYear(), ref.getMonth() + v + 1, targetDay);
            return localDateStr(d);
        } else if (def.intervalUnit === 'Jahre' && def.fixedDayOfMonth !== undefined && def.fixedDayOfMonth !== '' && def.fixedMonth !== undefined) {
            const targetDay = parseInt(def.fixedDayOfMonth);
            const targetMonth = parseInt(def.fixedMonth) - 1;
            let d = new Date(ref.getFullYear() + v, targetMonth, targetDay);
            if (d <= ref) d = new Date(ref.getFullYear() + v + 1, targetMonth, targetDay);
            return localDateStr(d);
        }
        return calculateNextDueDate(def.intervalValue, def.intervalUnit, referenceDateStr);
    }
    function calculateNextValidDateFromStart(startDateStr, intervalVal, intervalUnit) {
        let d = new Date(startDateStr);
        const now = new Date();
        const v = parseInt(intervalVal); 
        
        while (d < now) { 
            // Hier stand vorher fälschlicherweise "unit" statt "intervalUnit"
            if(intervalUnit === 'Wochen') d.setDate(d.getDate() + v * 7); 
            if(intervalUnit === 'Monate') d.setMonth(d.getMonth() + v); 
            if(intervalUnit === 'Jahre') d.setFullYear(d.getFullYear() + v); 
        } 
        return d.toISOString().split('T')[0]; 
    }    
    function createOrderFromDef(defId, dateOverride = null, silent = false) { 
        const def = taskDefinitions.find(d => d.id === defId); 
        if (!def) return;
        
        // --- DUPLIKAT-SCHUTZ ---
        const existingOpenOrder = maintenanceOrders.find(o => 
            o.parentTaskId === defId && o.status !== 'Completed'
        );

        if (existingOpenOrder) {
            if (!silent) {
                console.log(`Info: Es existiert bereits ein offener Auftrag (${existingOpenOrder.id}) für Plan ${defId}. Überspringe Erstellung.`);
            }
            return; // Abbruch
        }

        const newId = `ORD-${new Date().getFullYear()}-${String(Date.now()).slice(-4)}`; 
        const dueDate = dateOverride || new Date().toISOString().split('T')[0]; 
        
        const newOrder = { 
            id: newId, 
            type: 'M', 
            parentTaskId: def.id, 
            task: def.task, 
            description: def.description, 
            checklist: def.checklist ? [...def.checklist] : [], 
            checklistState: {}, 
            topic: def.topic, 
            testbench: def.testbench, 
            plantIndex: def.plantIndex,
            facilityUnitId: def.facilityUnitId, 
            maintenanceObjects: def.maintenanceObjects, 
            consumablesList: def.consumablesList, 
            requiresReview: def.requiresReview, 
            designatedReviewer: def.designatedReviewer, 
            externalCompanyId: def.externalCompanyId, 
            assigned: '', 
            date: dueDate, 
            status: 'Pending', 
            logs: [{ user: 'System', timestamp: new Date().toLocaleString(), note: 'Automatisch generiert aus Vorlage.' }] 
        }; 
        
        maintenanceOrders.push(newOrder);
        saveOrder(newOrder);
        renderOrdersTable();
    }

function openHistoryListModal(defId) {
        const def = taskDefinitions.find(d => d.id === defId);
        const title = def ? def.task : "Unbekannter Wartungsplan";
        
        // Titel setzen
        document.getElementById('historyModalTitle').textContent = title;

        const list = maintenanceOrders
            .filter(o => o.parentTaskId === defId && (o.status === 'Completed' || o.status === 'Review Ready'))
            .sort((a,b) => new Date(b.completedDate || b.date) - new Date(a.completedDate || a.date));
    
        // Statistik HTML berechnen
        let statsHtml = '';
        if (def && list.length >= 2) {
            const newestDate = new Date(list[0].completedDate || list[0].date);
            const oldestDate = new Date(list[list.length - 1].completedDate || list[list.length - 1].date);
            const diffMs = newestDate - oldestDate;
            const diffDays = diffMs / (1000 * 60 * 60 * 24);
            const avgDays = Math.round(diffDays / (list.length - 1));
            
            let targetDays = def.intervalValue * (def.intervalUnit === 'Jahre' ? 365 : (def.intervalUnit === 'Wochen' ? 7 : 30));
            let valueClass = "text-slate-700";
            if (avgDays > targetDays * 1.15) valueClass = "text-red-600 font-bold";
            else if (avgDays < targetDays * 0.85) valueClass = "text-blue-600 font-bold";
            
            // NUR der Inhalt der Pille (ohne Container-Div drumrum, wenn möglich, oder mit)
            // Hier nutzen wir direkt das Badge-Styling
            statsHtml = `
                <div class="inline-flex items-center px-3 py-1 rounded-full border border-slate-200 bg-slate-100 text-xs font-medium text-slate-500">
                    <span class="mr-2">Plan: ${def.intervalValue} ${def.intervalUnit}</span>
                    <span class="border-l border-slate-300 h-3 mx-2"></span>
                    <span>Ø Ist: <span class="${valueClass}">${avgDays} Tage</span></span>
                </div>`;
        } else if (def) {
            statsHtml = `
                <div class="inline-flex items-center px-3 py-1 rounded-full border border-slate-100 bg-slate-50 text-xs text-slate-400">
                    Plan: ${def.intervalValue} ${def.intervalUnit} (Basis < 2)
                </div>`;
        }

        // Badge befüllen (SICHER, da wir nur diesen Container anfassen)
        document.getElementById('historyStatsBadge').innerHTML = statsHtml;
    
        // ... (Rest der Funktion: Tabelle befüllen etc. bleibt gleich) ...
        const tbody = document.getElementById('historyListBody');
        tbody.innerHTML = '';
        
        if(list.length === 0) {
            tbody.innerHTML = '<tr><td colspan="4" class="px-6 py-12 text-center text-slate-400 italic flex flex-col items-center gap-2"><span class="material-icons-outlined text-3xl opacity-50">history_toggle_off</span><span>Keine abgeschlossenen Aufträge gefunden.</span></td></tr>';
        } else {
             list.forEach((o, index) => {
                let dateDisplay = o.completedDate || o.date;
                let intervalInfo = '';

                if (index < list.length - 1) {
                    const curr = new Date(o.completedDate || o.date);
                    const prev = new Date(list[index+1].completedDate || list[index+1].date);
                    const diff = Math.round((curr - prev) / (1000 * 60 * 60 * 24));
                    intervalInfo = `<div class="text-[10px] text-slate-400 mt-0.5">Δ ${diff} Tage</div>`;
                }

                try {
                    const d = new Date(dateDisplay);
                    if(!isNaN(d)) {
                        dateDisplay = d.toLocaleDateString('de-DE', { weekday: 'short', year: 'numeric', month: '2-digit', day: '2-digit' });
                    }
                } catch(e){}
    
                const user = o.completedBy || o.executedBy || 'Unbekannt';
                const initials = user.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
    
                let statusHtml = '<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 border border-green-200">Erledigt</span>';
                if (o.status === 'Review Ready') {
                    statusHtml = '<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 border border-purple-200">Prüfung</span>';
                }
    
                tbody.innerHTML += `
                    <tr class="hover:bg-slate-50 transition-colors group">
                        <td class="px-6 py-4">
                            <div class="flex flex-col">
                                ${statusHtml}
                                <span class="text-[10px] font-mono text-slate-400 mt-1">${o.id}</span>
                            </div>
                        </td>
                        <td class="px-6 py-4">
                            <div class="flex flex-col">
                                <div class="flex items-center text-slate-700 font-medium">
                                    <span class="material-icons-outlined text-slate-400 text-sm mr-2">event_available</span>
                                    ${dateDisplay}
                                </div>
                                ${intervalInfo}
                            </div>
                        </td>
                        <td class="px-6 py-4">
                            <div class="flex items-center gap-3">
                                <div class="w-8 h-8 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center text-xs font-bold border border-blue-200">${initials}</div>
                                <div class="text-sm text-slate-600">${user}</div>
                            </div>
                        </td>
                        <td class="px-6 py-4 text-right">
                            <button class="bg-white border border-slate-200 text-slate-600 hover:text-primary hover:border-primary hover:bg-blue-50 px-3 py-1.5 rounded-md text-xs font-medium transition-all shadow-sm flex items-center gap-2 ml-auto" onclick="closeHistoryListModal(); openWorkOrderModal('${o.id}')">
                                <span>Protokoll öffnen</span>
                                <span class="material-icons-outlined text-sm">open_in_new</span>
                            </button>
                        </td>
                    </tr>
                `;
            });
        }
    
        openModal('historyListModal');
    }
    function closeHistoryListModal() {
        closeModal('historyListModal');
    }
    let facilityUnitMap = {};
    
    async function loadFacilityUnitSuggestions() {
        try {
            const response = await fetch('/api/facility/units');
            if (response.ok) {
                const units = await response.json();
                
                let datalist = document.getElementById('facilityUnitList');
                if (!datalist) return; 
    
                datalist.innerHTML = ''; 
                facilityUnitMap = {}; // Reset
    
                units.forEach(unit => {
                    // Wir speichern das Mapping, um beim "Change"-Event die ID wiederzufinden
                    facilityUnitMap[unit.name] = unit.id;
                    
                    const option = document.createElement('option');
                    option.value = unit.name; 
                    // data-id im option Tag funktioniert in datalist leider nicht in allen Browsern zuverlässig beim Auslesen,
                    // daher nutzen wir die globale Map.
                    datalist.appendChild(option);
                });
            }
        } catch (error) {
            console.warn("Konnte Facility Units nicht laden:", error);
        }
    
    setTimeout(initFilterDropdowns, 500);
    }
    window.onFacilityUnitSelected = function(inputElement, targetIdField) {
        const selectedName = inputElement.value;
        const hiddenIdField = document.getElementById(targetIdField);
        
        if (hiddenIdField && facilityUnitMap[selectedName]) {
            hiddenIdField.value = facilityUnitMap[selectedName];
        } else if (hiddenIdField) {
            hiddenIdField.value = '';
        }
    };

    
    // --- HAUPTFUNKTION (Wird beim Tab-Wechsel aufgerufen) ---
    function renderMaintenanceAnalysis() {
        // --- DATENVORBEREITUNG ---
        const now = new Date();
        const oneYearAgo = new Date();
        oneYearAgo.setFullYear(now.getFullYear() - 1);
    
        // --- 1. KPI BERECHNUNG (REDUZIERT) ---
        let totalTime = 0;
        let timeCount = 0;
        let onTimeCount = 0;
        let totalMaint = 0;
    
        maintenanceOrders.forEach(o => {
            // Ø Lösungszeit (nur completed Issues)
            if(o.type === 'I' && o.status === 'Completed' && o.completedDate) {
                const start = new Date(o.date); 
                const end = new Date(o.completedDate);
                const diff = (end - start) / (1000 * 60 * 60 * 24);
                if(diff >= 0) { totalTime += diff; timeCount++; }
            }
            // On-Time Rate (nur completed Maintenance)
            if(o.type === 'M' && o.status === 'Completed' && o.completedDate) {
                const planned = new Date(o.date);
                const actual = new Date(o.completedDate);
                // Toleranz: 3 Tage
                const diff = (actual - planned) / (1000 * 60 * 60 * 24);
                if(diff <= 3) onTimeCount++;
                totalMaint++;
            }
        });
    
        const avgTime = timeCount > 0 ? (totalTime / timeCount).toFixed(1) : 0;
        const onTimeRate = totalMaint > 0 ? Math.round((onTimeCount / totalMaint) * 100) : 0;
    
        // DOM Updates KPIs
        const kpiAvgEl = document.getElementById('kpi-avg-time');
        const kpiOntimeEl = document.getElementById('kpi-ontime');
        if(kpiAvgEl) kpiAvgEl.textContent = avgTime + " Tage";
        if(kpiOntimeEl) kpiOntimeEl.textContent = onTimeRate + "%";
    
        // --- 2. ASSET HOTSPOT TABELLE (INTERAKTIV) ---
        // Wir rufen hier die neue Helper-Funktion auf, die Daten vorbereitet UND rendert
        prepareAssetData();
    
        // --- 3. PRODUCT TYPE CHART (Ersetzt Bereichs-Donut) ---
        const assetProductTypeMap = {};
        if (hardwareInventoryList) {
            hardwareInventoryList.forEach(h => {
                // Wir speichern unter Label UND ID, um sicher zu gehen
                if(h.label) assetProductTypeMap[h.label] = h.productType;
                if(h.id) assetProductTypeMap[h.id] = h.productType;
            });
        }
        
        const typeCounts = {};
        maintenanceOrders.filter(o => o.type === 'I').forEach(o => {
            const assets = [];
            if (o.affectedAssets) assets.push(...o.affectedAssets);
            if (o.maintenanceObjects) assets.push(...o.maintenanceObjects);
            
            assets.forEach(assetStr => {
                let lookupKey = assetStr;
                try { 
                    if(assetStr.startsWith('{')) {
                        const obj = JSON.parse(assetStr);
                        lookupKey = obj.id || obj.label; // Versuche erst ID, dann Label
                    }
                } catch(e){}
        
                const types = assetProductTypeMap[lookupKey] || ['Unbekannt/Sonstige'];
                const typeArray = Array.isArray(types) ? types : [types];
                
                typeArray.forEach(t => {
                    if(!t) t = 'Unbekannt';
                    typeCounts[t] = (typeCounts[t] || 0) + 1;
                });
            });
        });
        
        const sortedTypes = Object.entries(typeCounts).sort((a,b) => b[1] - a[1]);
        
        renderChart('chart-product-types', 'doughnut', {
            labels: sortedTypes.map(i => i[0]),
            datasets: [{
                data: sortedTypes.map(i => i[1]),
                backgroundColor: ['#3b82f6', '#f59e0b', '#10b981', '#ef4444', '#8b5cf6', '#64748b', '#06b6d4', '#f97316'],
                borderWidth: 0,
                cutout: '65%'
            }]
        }, { 
            plugins: { 
                legend: { position: 'right', labels: { usePointStyle: true, boxWidth: 8, font: { size: 11 } } } 
            } 
        });
        
        
        // --- NEU: BEREICHS-PERFORMANCE & CHART ---
        const areaStats = {};
        // Alle Aufträge durchgehen
        maintenanceOrders.forEach(o => {
            const area = o.topic || o.category || 'Allgemein';
            if (!areaStats[area]) {
                areaStats[area] = { totalIssues: 0, critical: 0, timeSum: 0, timeCount: 0, onTimeOk: 0, onTimeTotal: 0 };
            }
            const stats = areaStats[area];
        
            // Issues (Gesamt & Kritisch)
            if (o.type === 'I') {
                stats.totalIssues++;
                if (o.priority === 'Critical' || o.isBlocker) {
                    stats.critical++;
                }
                // Lösungszeit (nur wenn fertig)
                if (o.status === 'Completed' && o.completedDate) {
                    const diff = (new Date(o.completedDate) - new Date(o.date)) / (1000 * 60 * 60 * 24);
                    if(diff >= 0) { stats.timeSum += diff; stats.timeCount++; }
                }
            }
        
            // On-Time (Wartung)
            if (o.type === 'M' && o.status === 'Completed' && o.completedDate) {
                const diff = (new Date(o.completedDate) - new Date(o.date)) / (1000 * 60 * 60 * 24);
                if(diff <= 3) stats.onTimeOk++;
                stats.onTimeTotal++;
            }
        });
        
        // A) Tabelle füllen
        const areaTbody = document.getElementById('area-performance-table-body');
        if(areaTbody) {
            areaTbody.innerHTML = '';
            // Sortieren nach Total Issues (absteigend)
            Object.entries(areaStats).sort((a,b) => b[1].totalIssues - a[1].totalIssues).forEach(([area, s]) => {
                const avgTime = s.timeCount > 0 ? (s.timeSum / s.timeCount).toFixed(1) + 'd' : '-';
                const onTimePct = s.onTimeTotal > 0 ? Math.round((s.onTimeOk / s.onTimeTotal) * 100) : 0;
                
                let barColor = 'bg-green-500';
                if (onTimePct < 80) barColor = 'bg-yellow-500';
                if (onTimePct < 50) barColor = 'bg-red-500';
                
                const critBadge = s.critical > 0 
                    ? `<span class="bg-red-100 text-red-700 px-2 py-0.5 rounded-full text-[10px] font-bold">${s.critical}</span>`
                    : `<span class="text-slate-300">-</span>`;
        
                const tr = document.createElement('tr');
                tr.className = "hover:bg-slate-50 transition-colors border-b border-slate-100 last:border-0";
                tr.innerHTML = `
                    <td class="px-4 py-3 font-medium text-slate-800">${area}</td>
                    <td class="px-4 py-3 text-center font-bold text-slate-600">${s.totalIssues}</td>
                    <td class="px-4 py-3 text-center">${critBadge}</td>
                    <td class="px-4 py-3 text-center text-slate-600">${avgTime}</td>
                    <td class="px-4 py-3 align-middle">
                        <div class="flex items-center gap-3">
                            <div class="w-full bg-slate-200 rounded-full h-1.5 flex-grow">
                                <div class="${barColor} h-1.5 rounded-full" style="width: ${s.onTimeTotal > 0 ? onTimePct + '%' : '0%'}"></div>
                            </div>
                            <span class="text-xs font-medium text-slate-500 w-8 text-right">${s.onTimeTotal > 0 ? onTimePct + '%' : '-'}</span>
                        </div>
                    </td>
                `;
                areaTbody.appendChild(tr);
            });
        }
        
        // B) Donut Chart (Wiederhergestellt)
        const sortedAreas = Object.entries(areaStats).sort((a,b) => b[1].totalIssues - a[1].totalIssues);
        renderChart('chart-areas', 'doughnut', {
            labels: sortedAreas.map(i => i[0]),
            datasets: [{
                data: sortedAreas.map(i => i[1].totalIssues),
                backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444', '#64748b'],
                borderWidth: 0,
                cutout: '70%'
            }]
        }, { 
            plugins: { 
                legend: { position: 'bottom', labels: { usePointStyle: true, padding: 20, font: { size: 11 } } } 
            } 
        });
        
    
        // --- 4. TREND-CHART (Line) ---
        const trendData = {};
        maintenanceOrders.filter(o => o.status === 'Completed' && o.completedDate).forEach(o => {
            const d = new Date(o.completedDate);
            if(d < oneYearAgo) return;
            const key = d.toLocaleString('de-DE', { month: 'short', year: '2-digit' });
            
            if(!trendData[key]) trendData[key] = { delaySum: 0, count: 0 };
            const planned = new Date(o.date);
            const diff = (d - planned) / (1000 * 60 * 60 * 24);
            trendData[key].delaySum += Math.max(0, diff);
            trendData[key].count++;
        });
        
        const trendLabels = [];
        const trendValues = [];
        for(let i=11; i>=0; i--) {
            const d = new Date();
            d.setMonth(d.getMonth() - i);
            const key = d.toLocaleString('de-DE', { month: 'short', year: '2-digit' });
            trendLabels.push(key);
            const data = trendData[key];
            trendValues.push(data ? (data.delaySum / data.count).toFixed(1) : 0);
        }
    
        renderChart('chart-trend', 'line', {
            labels: trendLabels,
            datasets: [{
                label: 'Ø Verspätung (Tage)',
                data: trendValues,
                borderColor: '#3b82f6',
                backgroundColor: 'rgba(59, 130, 246, 0.1)',
                fill: true,
                tension: 0.4,
                pointRadius: 4,
                pointHoverRadius: 6
            }]
        }, { maintainAspectRatio: false });
    
        // --- 5. INTERVALL-CHART (Scatter) ---
        const scatterData = [];
        taskDefinitions.forEach(def => {
            const orders = maintenanceOrders
                .filter(o => o.parentTaskId === def.id && o.status === 'Completed')
                .sort((a,b) => new Date(a.completedDate) - new Date(b.completedDate));
            
            if(orders.length >= 2) {
                const newest = new Date(orders[orders.length-1].completedDate);
                const oldest = new Date(orders[0].completedDate);
                const diffDays = (newest - oldest) / (1000 * 60 * 60 * 24);
                const avgIst = diffDays / (orders.length - 1);
                let avgSoll = def.intervalValue * (def.intervalUnit === 'Jahre' ? 365 : (def.intervalUnit === 'Wochen' ? 7 : 30));
                
                scatterData.push({ x: avgSoll, y: avgIst, label: def.task });
            }
        });
    
        renderChart('chart-intervals', 'scatter', {
            datasets: [
                { label: 'Wartungspläne', data: scatterData, backgroundColor: '#8b5cf6', pointRadius: 5, pointHoverRadius: 7 },
                { label: 'Ideal-Linie', data: [{x: 0, y: 0}, {x: 365, y: 365}], type: 'line', borderColor: '#e5e7eb', borderDash: [5, 5], pointRadius: 0 }
            ]
        }, {
            maintainAspectRatio: false,
            plugins: {
                tooltip: {
                    callbacks: {
                        label: function(context) {
                            const p = context.raw;
                            if(!p.label) return '';
                            const abw = Math.round(((p.y - p.x) / p.x) * 100);
                            return `${p.label}: Soll ${p.x}d / Ist ${Math.round(p.y)}d (${abw > 0 ? '+' : ''}${abw}%)`;
                        }
                    }
                }
            }
        });
    // --- NEU: HISTORIE NACH BETRIEBSBEREICH ---
        const historyUnitStats = {};
        
        maintenanceOrders.filter(o => o.status === 'Completed').forEach(o => {
            // Wir gruppieren nach plantIndex. Falls leer -> "Ohne Zuordnung"
            const unit = o.plantIndex || 'Ohne Zuordnung';
            
            if (!historyUnitStats[unit]) {
                historyUnitStats[unit] = { name: unit, total: 0, issues: 0, lastDate: null, history: [] };
            }
            
            historyUnitStats[unit].total++;
            if (o.type === 'I') historyUnitStats[unit].issues++;
            
            // Datum des Abschlusses für "Zuletzt am"
            const doneDate = new Date(o.completedDate || o.date);
            if(!historyUnitStats[unit].lastDate || doneDate > historyUnitStats[unit].lastDate) {
                historyUnitStats[unit].lastDate = doneDate;
            }
            
            // Wir speichern die ID für das Modal
            historyUnitStats[unit].history.push(o.id);
        });
    
        // A) Tabelle rendern
        const historyTbody = document.getElementById('history-unit-table-body');
        if(historyTbody) {
            historyTbody.innerHTML = '';
            const sortedHistory = Object.values(historyUnitStats).sort((a,b) => b.total - a.total);
            
            if(sortedHistory.length === 0) {
                historyTbody.innerHTML = '<tr><td colspan="4" class="py-8 text-center text-slate-400 italic">Keine Historie vorhanden</td></tr>';
            }
    
            sortedHistory.forEach(stat => {
                const tr = document.createElement('tr');
                tr.className = "hover:bg-slate-50 transition-colors border-b border-slate-100 last:border-0 group cursor-pointer";
                // Wir nutzen eine neue Funktion openUnitHistory, die intern aber das assetModal nutzt
                tr.onclick = () => openUnitHistory(stat.name, stat.history);
                
                tr.innerHTML = `
                    <td class="px-4 py-3">
                        <div class="font-medium text-purple-600 group-hover:text-purple-800 flex items-center gap-2">
                            ${stat.name}
                            <span class="material-icons-outlined text-xs opacity-0 group-hover:opacity-100 transition-opacity">visibility</span>
                        </div>
                    </td>
                    <td class="px-4 py-3 text-center font-bold text-slate-600">${stat.total}</td>
                    <td class="px-4 py-3 text-center text-slate-500">${stat.issues > 0 ? stat.issues : '-'}</td>
                    <td class="px-4 py-3 text-right text-xs text-slate-500">${stat.lastDate ? stat.lastDate.toLocaleDateString('de-DE') : '-'}</td>
                `;
                historyTbody.appendChild(tr);
            });
            
            // B) Donut Chart rendern
            // Wir nehmen nur die Top 10 für den Chart, sonst wird es zu bunt
            const chartData = sortedHistory.slice(0, 10);
            
            renderChart('chart-history-units', 'doughnut', {
                labels: chartData.map(i => i.name),
                datasets: [{
                    data: chartData.map(i => i.total),
                    backgroundColor: ['#8b5cf6', '#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#64748b', '#06b6d4', '#f97316'],
                    borderWidth: 0,
                    cutout: '65%'
                }]
            }, { 
                plugins: { 
                    legend: { position: 'right', labels: { usePointStyle: true, boxWidth: 8, font: { size: 10 } } } 
                } 
            });
        }    
        // --- 6. PARTNER TABELLE ---
        renderPartnerTable();
    }
    // Öffnet das Asset-History-Modal, aber für einen ganzen Betriebsbereich (Unit)
    function openUnitHistory(unitName, orderIds) {
        // Titel setzen
        document.getElementById('assetHistoryTitle').textContent = unitName;
        document.getElementById('assetHistorySubtitle').textContent = `${orderIds.length} abgeschlossene Aufträge`;
        
        const container = document.getElementById('assetHistoryTimeline');
        container.innerHTML = '';
    
        // Orders holen und sortieren (Neu -> Alt)
        const historyOrders = maintenanceOrders.filter(o => orderIds.includes(o.id));
        historyOrders.sort((a,b) => new Date(b.completedDate || b.date) - new Date(a.completedDate || a.date));
    
        // Rendering-Logik ist exakt gleich wie bei Assets, daher kopieren wir den Block oder nutzen Helper.
        // Hier der Einfachheit halber der Block (da er kurz ist):
        historyOrders.forEach(o => {
            const dateObj = new Date(o.completedDate || o.date);
            const dateStr = dateObj.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' });
            
            let dotColor = "bg-green-500 ring-green-200";
            let icon = "check_circle";
            if(o.type === 'I') { dotColor = "bg-orange-500 ring-orange-200"; icon = "build"; } // Issue gefixt
            
            const item = document.createElement('div');
            item.className = "ml-6 relative";
            item.innerHTML = `
                <span class="absolute -left-[31px] top-1 flex h-6 w-6 items-center justify-center rounded-full ring-4 ring-white ${dotColor} text-white">
                    <span class="material-icons-outlined text-[14px]">${icon}</span>
                </span>
                <div class="bg-white p-4 rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer" onclick="openWorkOrderModal('${o.id}')">
                    <div class="flex justify-between items-start mb-1">
                        <span class="text-xs font-bold text-slate-400 uppercase tracking-wider">${dateStr}</span>
                        <span class="text-[10px] bg-slate-100 text-slate-600 px-2 py-0.5 rounded-full border border-slate-200">Erledigt</span>
                    </div>
                    <h4 class="font-bold text-slate-800 text-sm mb-1">${o.task}</h4>
                    <div class="mt-2 flex items-center gap-2 text-[10px] text-slate-400 border-t border-slate-100 pt-2">
                        <span>${o.type === 'I' ? 'Issue Behebung' : 'Planmäßige Wartung'}</span>
                        <span>•</span>
                        <span>Von: ${o.completedBy || 'Unbekannt'}</span>
                    </div>
                </div>
            `;
            container.appendChild(item);
        });
    
        document.getElementById('assetHistoryModal').classList.remove('hidden');
    }    
    // --- HELPER 1: Daten aufbereiten für Asset Tabelle ---
    function prepareAssetData() {
        const assetStats = {};
        maintenanceOrders.filter(o => o.type === 'I').forEach(o => {
            const assets = [];
            if (o.affectedAssets) assets.push(...o.affectedAssets);
            if (o.maintenanceObjects) assets.push(...o.maintenanceObjects);
            
            assets.forEach(assetStr => {
                let label = assetStr;
                try { if(assetStr.startsWith('{')) label = JSON.parse(assetStr).label; } catch(e){}
                
                if(!assetStats[label]) {
                    assetStats[label] = { name: label, total: 0, critical: 0, lastDate: null, history: [] };
                }
                assetStats[label].total++;
                if(o.priority === 'Critical' || o.isBlocker) assetStats[label].critical++;
                
                const orderDate = new Date(o.date);
                if(!assetStats[label].lastDate || orderDate > assetStats[label].lastDate) {
                    assetStats[label].lastDate = orderDate;
                }
                assetStats[label].history.push(o.id);
            });
        });
    
        globalAssetStats = Object.values(assetStats).sort((a,b) => b.total - a.total || b.critical - a.critical);
        renderAssetTable();
    }
    
    // --- HELPER 2: Asset Tabelle rendern (mit Suche) ---
    function renderAssetTable() {
        const searchTerm = document.getElementById('asset-search-input')?.value.toLowerCase() || '';
        const tableBody = document.getElementById('asset-hotspot-table-body');
        if(!tableBody) return;
        
        tableBody.innerHTML = '';
        
        const filtered = globalAssetStats.filter(a => a.name.toLowerCase().includes(searchTerm));
        const displayList = filtered.slice(0, 50);
    
        if (displayList.length === 0) {
            tableBody.innerHTML = '<tr><td colspan="4" class="py-8 text-center text-slate-400 italic">Keine passenden Assets gefunden</td></tr>';
            return;
        }
    
        displayList.forEach(stat => {
            const tr = document.createElement('tr');
            tr.className = "hover:bg-slate-50 transition-colors border-b border-slate-100 last:border-0 group cursor-pointer";
            tr.onclick = () => openAssetHistory(stat.name);
            
            const critBadge = stat.critical > 0 
                ? `<span class="bg-red-100 text-red-700 px-2 py-0.5 rounded-full text-[10px] font-bold">${stat.critical}</span>`
                : `<span class="text-slate-300">-</span>`;
    
            tr.innerHTML = `
                <td class="px-4 py-3">
                    <div class="font-medium text-blue-600 group-hover:text-blue-800 flex items-center gap-2">
                        ${stat.name}
                        <span class="material-icons-outlined text-xs opacity-0 group-hover:opacity-100 transition-opacity">visibility</span>
                    </div>
                </td>
                <td class="px-4 py-3 text-center font-bold text-slate-600">${stat.total}</td>
                <td class="px-4 py-3 text-center">${critBadge}</td>
                <td class="px-4 py-3 text-right text-xs text-slate-500">${stat.lastDate ? stat.lastDate.toLocaleDateString('de-DE') : '-'}</td>
            `;
            tableBody.appendChild(tr);
        });
        
        // --- NEU: FOOTER UPDATE ---
        const footer = document.getElementById('asset-table-footer');
        if(footer) {
            if (filtered.length > 50) {
                footer.textContent = `Zeige Top 50 von ${filtered.length} Assets. Bitte Suche verfeinern.`;
                footer.classList.remove('hidden');
            } else {
                footer.textContent = '';
                footer.classList.add('hidden');
            }
        }
    }
    function renderPartnerTable() {
        const tbody = document.getElementById('partner-table-body');
        if(!tbody) return;
        tbody.innerHTML = '';
        
        // Daten aggregieren
        const partnerStats = {};
        maintenanceOrders.forEach(o => {
            if(!o.externalCompanyId) return;
            
            if(!partnerStats[o.externalCompanyId]) {
                partnerStats[o.externalCompanyId] = { count: 0, delaySum: 0, delayCount: 0, open: 0 };
            }
            const stats = partnerStats[o.externalCompanyId];
            
            if(o.status === 'Completed' && o.completedDate) {
                stats.count++;
                const plan = new Date(o.date);
                const done = new Date(o.completedDate);
                const delay = (done - plan) / (1000*60*60*24);
                stats.delaySum += delay; 
                stats.delayCount++;
            } else if (o.status !== 'Completed') {
                stats.open++;
            }
        });
    
        if(companies.length === 0) {
            tbody.innerHTML = '<tr><td colspan="5" class="py-8 text-center text-slate-400 italic">Keine externen Firmen definiert</td></tr>';
            return;
        }
    
        // Rendern
        companies.forEach(comp => {
            const stats = partnerStats[comp.id] || { count: 0, delaySum: 0, delayCount: 0, open: 0 };
            const avgDelay = stats.delayCount > 0 ? (stats.delaySum / stats.delayCount) : 0;
            
            // Pünktlichkeits-Balken Logik
            let barColor = 'bg-green-500';
            let barWidth = '50%'; // Mitte = Pünktlich (0 Tage)
            
            // Mapping: -5 Tage (Früh) bis +10 Tage (Spät)
            // Wir verschieben den Nullpunkt (50%) je nach Delay
            // Ein Tag = 5% Verschiebung
            let shift = avgDelay * 5; 
            let widthVal = 50 + shift;
            
            if (widthVal > 100) widthVal = 100;
            if (widthVal < 0) widthVal = 0;
            
            if (avgDelay > 2) barColor = 'bg-yellow-500';
            if (avgDelay > 7) barColor = 'bg-red-500';
            if (avgDelay <= 0) barColor = 'bg-green-600';
    
            barWidth = widthVal + '%';
    
            const tr = document.createElement('tr');
            tr.className = "hover:bg-slate-50 border-b border-slate-100 last:border-0 transition-colors";
            tr.innerHTML = `
                <td class="px-6 py-4 font-medium text-slate-800">${comp.name}</td>
                <td class="px-6 py-4 text-center">${stats.count}</td>
                <td class="px-6 py-4 text-center text-slate-500">${stats.delayCount > 0 ? Math.round(avgDelay) + 'd' : '-'}</td>
                <td class="px-6 py-4 align-middle">
                    <div class="w-full bg-slate-200 rounded-full h-1.5 relative">
                        <!-- Marker für "Heute/Pünktlich" in der Mitte -->
                        <div class="absolute left-1/2 top-0 bottom-0 w-0.5 bg-slate-400 -ml-[1px]"></div>
                        <div class="${barColor} h-1.5 rounded-full transition-all duration-500" style="width: ${barWidth}"></div>
                    </div>
                </td>
                <td class="px-6 py-4 text-center font-bold ${stats.open > 0 ? 'text-blue-600' : 'text-slate-400'}">${stats.open}</td>
            `;
            tbody.appendChild(tr);
        });
    }
    loadFacilityUnitSuggestions();
    function calculateNextDueDate(val, unit, baseDateStr) { let d = baseDateStr ? new Date(baseDateStr) : new Date(); if(isNaN(d.getTime())) d = new Date(); const v = parseInt(val); if(unit === 'Wochen') d.setDate(d.getDate() + v * 7); if(unit === 'Monate') d.setMonth(d.getMonth() + v); if(unit === 'Jahre') d.setFullYear(d.getFullYear() + v); return d.toISOString().split('T')[0]; }
    async function loadLogs() {
        try {
            const res = await fetch('/api/maintenance/logs');
            if(res.ok) maintenanceLogs = await res.json();
        } catch(e) { console.error(e); }
    }
    function renderLogbook() {
        const container = document.getElementById('logbook-container');
        const searchTerm = document.getElementById('logbook-search').value.toLowerCase();
        container.innerHTML = '';
    
        // Filtern

        const filtered = maintenanceLogs.filter(l => 
            (l.task && l.task.toLowerCase().includes(searchTerm)) || 
            (l.description && l.description.toLowerCase().includes(searchTerm)) ||
            (l.category && l.category.toLowerCase().includes(searchTerm)) ||
            (l.plantIndex && l.plantIndex.toLowerCase().includes(searchTerm)) // <--- NEU
        );
    
        // Sortieren (Neu -> Alt)
        filtered.sort((a,b) => new Date(b.date) - new Date(a.date));
    
        // Gruppieren nach Monat (YYYY-MM)
        const groups = {};
        filtered.forEach(log => {
            const d = new Date(log.date);
            const key = d.toLocaleString('de-DE', { month: 'long', year: 'numeric' });
            if(!groups[key]) groups[key] = [];
            groups[key].push(log);
        });
    
        if (Object.keys(groups).length === 0) {
            container.innerHTML = '<p class="text-center text-slate-400 italic py-8">Keine Einträge gefunden.</p>';
            return;
        }
    
        // Rendern
        for (const [month, logs] of Object.entries(groups)) {
            const groupHtml = `
                <div class="bg-white rounded-lg shadow border border-slate-200 overflow-hidden">
                    <div class="bg-slate-50 px-4 py-2 border-b border-slate-200 font-bold text-slate-700 sticky top-0">
                        ${month} <span class="ml-2 text-xs font-normal text-slate-500 bg-white px-2 py-0.5 rounded border border-slate-200">${logs.length}</span>
                    </div>
                    <div class="divide-y divide-slate-100">
                        ${logs.map(l => {
                            const dateStr = new Date(l.date).toLocaleDateString('de-DE');
                            const user = l.createdBy || 'Unbekannt';
                            const initials = user.split(' ').map(n=>n[0]).join('').substring(0,2).toUpperCase();
                            
                            let plantBadge = '';
                            if (l.plantIndex) {
                                plantBadge = `<span class="text-[10px] px-2 py-0.5 bg-slate-100 text-slate-600 rounded border border-slate-200 font-mono" title="Betriebsbereich"><span class="material-icons-outlined text-[10px] align-middle mr-1">place</span>${l.plantIndex}</span>`;
                            }
    
                            return `
                            <div class="p-4 hover:bg-slate-50 transition-colors cursor-pointer" onclick="openLogEntry('${l.id}')">
                                <div class="flex justify-between items-start mb-1">
                                    <div class="flex items-center gap-2 flex-wrap">
                                        <span class="text-sm font-bold text-slate-800">${l.task}</span>
                                        <span class="text-[10px] px-2 py-0.5 bg-indigo-50 text-indigo-700 rounded border border-indigo-100">${l.category || 'Allgemein'}</span>
                                        ${plantBadge} <!-- NEU -->
                                    </div>
                                    <span class="text-xs text-slate-400 whitespace-nowrap ml-2">${dateStr}</span>
                                </div>
                                <p class="text-sm text-slate-600 mb-2 whitespace-pre-wrap">${l.description || ''}</p>
                                <div class="flex items-center gap-2 text-xs text-slate-400">
                                    <div class="w-5 h-5 rounded-full bg-slate-200 text-slate-600 flex items-center justify-center font-bold text-[10px]">${initials}</div>
                                    <span>${user}</span>
                                    ${l.attachments && l.attachments.length > 0 ? `<span class="ml-auto inline-flex items-center gap-0.5 text-indigo-500"><span class="material-icons-outlined text-[13px]">attach_file</span>${l.attachments.length}</span>` : ''}
                                </div>
                            </div>`;
                        }).join('')}
                    </div>
                </div>`;
            container.innerHTML += groupHtml;
        }
    }
    
    function openCreateLogModal() {
        openLogEntry(null);
    }
    let currentLogAttachments = []; // {name, path} – bereits gespeicherte Dateien
    let pendingLogFiles = [];       // File-Objekte – noch nicht hochgeladen

    function openLogEntry(logId = null) {
        document.getElementById('createLogForm').reset();
        document.getElementById('logId').value = '';
        document.getElementById('btnDeleteLog').classList.add('hidden');
        document.getElementById('logMetaInfo').classList.add('hidden');
        document.getElementById('logPlantIndex').value = '';
        currentLogAttachments = [];
        pendingLogFiles = [];
        renderLogAttachmentChips();

        // Kategorie Dropdown füllen (falls noch leer)
        // ... (Logik wie bisher) ...

        if (logId) {
            // EDIT MODE
            const log = maintenanceLogs.find(l => l.id === logId);
            if(!log) return;

            document.getElementById('logModalTitle').textContent = "Eintrag bearbeiten";
            document.getElementById('logId').value = log.id;
            document.getElementById('logTitle').value = log.task;
            document.getElementById('logDate').value = log.date;
            document.getElementById('logCategory').value = log.category || 'Allgemein';
            document.getElementById('logDesc').value = log.description;
            document.getElementById('logPlantIndex').value = log.plantIndex || '';
            currentLogAttachments = log.attachments ? [...log.attachments] : [];
            renderLogAttachmentChips();

            // Meta-Info
            document.getElementById('logCreatedBy').textContent = log.createdBy || 'Unbekannt';
            document.getElementById('logCreatedAt').textContent = new Date(log.createdAt).toLocaleString('de-DE');
            document.getElementById('logMetaInfo').classList.remove('hidden');

            // Löschen erlauben (für Admin oder Ersteller)
            if(currentUserIsAdmin || log.createdBy === currentUser) {
                 document.getElementById('btnDeleteLog').classList.remove('hidden');
            }
        } else {
            // CREATE MODE
            document.getElementById('logModalTitle').textContent = "Neuer Logbucheintrag";
            document.getElementById('logDate').value = new Date().toISOString().split('T')[0];
        }

        openModal('createLogModal');
    }

    function handleLogDrop(event) {
        event.preventDefault();
        document.getElementById('logDropZone').classList.remove('border-indigo-500', 'bg-indigo-50');
        const files = Array.from(event.dataTransfer.files);
        files.forEach(f => pendingLogFiles.push(f));
        renderLogAttachmentChips();
    }

    function handleLogFileSelect(input) {
        Array.from(input.files).forEach(f => pendingLogFiles.push(f));
        input.value = '';
        renderLogAttachmentChips();
    }

    function renderLogAttachmentChips() {
        const container = document.getElementById('logAttachmentChips');
        if (!container) return;
        const savedChips = currentLogAttachments.map((a, i) => `
            <span class="inline-flex items-center gap-1 px-2 py-1 bg-indigo-50 border border-indigo-200 rounded text-xs text-indigo-700">
                <span class="material-icons-outlined text-[13px]">attach_file</span>
                <a href="${a.path}" target="_blank" class="hover:underline max-w-[160px] truncate">${a.name}</a>
                <button type="button" onclick="removeLogSavedAttachment(${i})" class="ml-1 text-indigo-400 hover:text-red-500">
                    <span class="material-icons-outlined text-[13px]">close</span>
                </button>
            </span>`).join('');
        const pendingChips = pendingLogFiles.map((f, i) => `
            <span class="inline-flex items-center gap-1 px-2 py-1 bg-amber-50 border border-amber-200 rounded text-xs text-amber-700">
                <span class="material-icons-outlined text-[13px]">upload</span>
                <span class="max-w-[160px] truncate">${f.name}</span>
                <button type="button" onclick="removeLogPendingFile(${i})" class="ml-1 text-amber-400 hover:text-red-500">
                    <span class="material-icons-outlined text-[13px]">close</span>
                </button>
            </span>`).join('');
        container.innerHTML = savedChips + pendingChips;
    }

    function removeLogSavedAttachment(index) {
        const removed = currentLogAttachments.splice(index, 1)[0];
        fetch(`/api/maintenance/logbook/upload/${encodeURIComponent(removed.name)}`, { method: 'DELETE' });
        renderLogAttachmentChips();
    }

    function removeLogPendingFile(index) {
        pendingLogFiles.splice(index, 1);
        renderLogAttachmentChips();
    }

    async function uploadPendingLogFiles() {
        const results = [];
        for (const file of pendingLogFiles) {
            const formData = new FormData();
            formData.append('file', file);
            try {
                const res = await fetch('/api/maintenance/logbook/upload', { method: 'POST', body: formData });
                if (res.ok) {
                    const data = await res.json();
                    results.push({ name: file.name, path: data.path });
                }
            } catch(e) { console.error('Logbuch-Upload fehlgeschlagen:', file.name); }
        }
        return results;
    }
        
    function closeCreateLogModal() { closeModal('createLogModal'); }
    
    async function submitLogEntry() {
        const title = document.getElementById('logTitle').value.trim();
        if(!title) { alert("Titel fehlt."); return; }

        const btn = document.getElementById('btnSaveLog');
        btn.disabled = true;
        btn.innerHTML = '<span class="material-icons-outlined text-sm animate-spin">autorenew</span> Speichern...';

        const newUploads = await uploadPendingLogFiles();
        const allAttachments = [...currentLogAttachments, ...newUploads];

        const id = document.getElementById('logId').value;

        if (id) {
            // UPDATE
            const log = maintenanceLogs.find(l => l.id === id);
            if(log) {
                log.task = title;
                log.date = document.getElementById('logDate').value;
                log.category = document.getElementById('logCategory').value;
                log.description = document.getElementById('logDesc').value;
                log.plantIndex = document.getElementById('logPlantIndex').value;
                log.attachments = allAttachments;
            }
        } else {
            // CREATE
            const newLog = {
                id: `LOG-${Date.now()}`,
                type: 'L',
                task: title,
                description: document.getElementById('logDesc').value,
                category: document.getElementById('logCategory').value,
                date: document.getElementById('logDate').value,
                plantIndex: document.getElementById('logPlantIndex').value,
                attachments: allAttachments,
                createdBy: currentUser,
                createdAt: new Date().toISOString()
            };
            maintenanceLogs.unshift(newLog);
        }

        try {
            await fetch('/api/maintenance/logs', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({ logs: maintenanceLogs })
            });
            closeCreateLogModal();
            renderLogbook();
        } catch(e) { alert("Fehler beim Speichern"); }
        finally {
            btn.disabled = false;
            btn.innerHTML = '<span class="material-icons-outlined text-sm">save</span> Speichern';
        }
    }
    async function deleteLogEntry() {
        const id = document.getElementById('logId').value;
        if(!id) return;

        if(confirm("Diesen Eintrag wirklich löschen? Anhänge werden ebenfalls gelöscht.")) {
            const log = maintenanceLogs.find(l => l.id === id);
            if(log && log.attachments) {
                for(const a of log.attachments) {
                    await fetch(`/api/maintenance/logbook/upload/${encodeURIComponent(a.name)}`, { method: 'DELETE' });
                }
            }
            maintenanceLogs = maintenanceLogs.filter(l => l.id !== id);

            try {
                await fetch('/api/maintenance/logs', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({ logs: maintenanceLogs })
                });
                closeCreateLogModal();
                renderLogbook();
            } catch(e) { alert("Fehler beim Löschen"); }
        }
    }
    // --- 4. INITIALISIERUNG (Wenn die Seite geladen ist) ---
    document.addEventListener('DOMContentLoaded', () => {
        console.log("Daten geladen, starte Initialisierung...");

        // --- SIDEBAR LOGIC (NEU EINFÜGEN) ---
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if (sidebar && sidebarToggleBtn) {
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            
            const applyState = (isCollapsed) => {
                if (isCollapsed) {
                    sidebar.classList.add('is-collapsed');
                    if(toggleBtnIcon) toggleBtnIcon.textContent = 'menu';
                } else {
                    sidebar.classList.remove('is-collapsed');
                    if(toggleBtnIcon) toggleBtnIcon.textContent = 'chevron_left';
                }
            };

            sidebarToggleBtn.addEventListener('click', () => {
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed);
                applyState(isNowCollapsed);
            });

            // Initialen Status laden
            const savedState = localStorage.getItem('sidebarCollapsed') === 'true';
            applyState(savedState);
        }

        const GLOBAL_AREAS =[
            "Anlagen / Mechanik", "Elektrik / Messtechnik", "Test Engineering",
            "Facility Management", "IT / Software", "Allgemein"
        ];
        
        function initAreaDropdowns() {
            const targets = ['defTopic', 'issueCategory', 'actionCategory', 'woEditCategory', 'logCategory'];
            targets.forEach(id => {
                const el = document.getElementById(id);
                if(el) {
                    const currentVal = el.value; 
                    el.innerHTML = '';
                    GLOBAL_AREAS.forEach(area => {
                        const opt = document.createElement('option');
                        opt.value = area; opt.textContent = area; el.appendChild(opt);
                    });
                    if(currentVal) el.value = currentVal;
                }
            });
        }

        initAreaDropdowns();

        // Hier werden die Listen gefüllt (Dein bestehender Code)
        loadTestBenchOptions();
        loadHardwareInventory();
        loadFacilityUnitSuggestions();
        loadLogs();
        
        // Tabellen initial rendern
        renderOrdersTable();
        renderDefinitionsTable();
        initFilterDropdowns(); // Wichtig für deine neuen Filter-Chips!

        // --- 5. DEEP LINK HANDLING (Innerhalb von DOMContentLoaded aufrufen) ---
        handleDeepLink();
    });

function handleDeepLink() {
        // Wir warten einen Moment, bis das initiale Rendering der Seite fertig ist
        setTimeout(() => {
            try {
                const urlParams = new URLSearchParams(window.location.search);
                const openId = urlParams.get('openId');

                if (openId) {
                    console.log("Deep Link: ID gefunden", openId);

                    // URL bereinigen
                    const cleanUrl = window.location.pathname;
                    window.history.replaceState({}, document.title, cleanUrl);

                    // Daten suchen
                    const def = taskDefinitions.find(d => d.id === openId);
                    const order = maintenanceOrders.find(o => o.id === openId);
                    
                    let targetTabName = 'active';

                    if (def) {
                        targetTabName = 'definitions';
                    } else if (order) {
                        if (order.status === 'Completed') {
                            targetTabName = 'history';
                        }
                    }

                    // --- SCHRITT 1: MODAL SOFORT ÖFFNEN ---
                    // Wir öffnen das Modal unabhängig vom Tab-Status.
                    // Das Overlay liegt eh über allem.
                    console.log("Deep Link: Öffne Modal PRIORITÄT 1");
                    
                    if (def) {
                        if(typeof openDefinitionModal === 'function') openDefinitionModal(openId);
                    } else if (order) {
                        if(typeof openWorkOrderModal === 'function') {
                            currentOrderId = openId;
                            openWorkOrderModal(openId);
                        }
                    }

                    // --- SCHRITT 2: TAB WECHSEL (Verzögert) ---
                    // Wir machen das erst, wenn das Modal sicher offen ist.
                    // Wenn das hier crasht, sieht der User wenigstens schon das Modal.
                    setTimeout(() => {
                        console.log(`Deep Link: Versuche Tab im Hintergrund zu wechseln auf '${targetTabName}'`);
                        try {
                            // Button finden für Styling
                            const allTabs = Array.from(document.querySelectorAll('.tab-btn'));
                            let targetBtn = allTabs.find(btn => btn.getAttribute('onclick')?.includes(`'${targetTabName}'`));
                            
                            // Fallback
                            if (!targetBtn && targetTabName === 'history') targetBtn = allTabs[3];

                            if (typeof switchTab === 'function') {
                                switchTab(targetTabName, targetBtn);
                            }
                        } catch (tabErr) {
                            console.warn("Deep Link: Tab-Wechsel im Hintergrund fehlgeschlagen (nicht kritisch):", tabErr);
                        }
                    }, 800); // 800ms warten - Modal ist dann schon da
                }
            } catch (e) {
                console.error("Deep Link CRITICAL ERROR:", e);
            }
        }, 100);
    }
</script>
</body>
</html>
"""
FACILITY_REPORT_TEMPLATE = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>Anlagen-Audit Report</title>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <style>
        body { font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 12px; color: #333; padding: 20px; }
        h1 { margin-bottom: 5px; font-size: 24px; color: #0055A4; border-bottom: 2px solid #0055A4; padding-bottom: 10px; }
        h2 { margin-top: 30px; margin-bottom: 10px; font-size: 18px; background: #eee; padding: 8px; border-left: 5px solid #0055A4; }
        .meta-info { color: #666; margin-bottom: 20px; font-size: 11px; }

        table { width: 100%; border-collapse: collapse; margin-bottom: 20px; page-break-inside: auto; }
        th, td { border: 1px solid #ddd; padding: 8px; vertical-align: top; text-align: left; }
        th { background-color: #f8f9fa; font-weight: bold; color: #495057; }
        tr { page-break-inside: avoid; page-break-after: auto; }

        .node-name { font-weight: 600; font-size: 13px; }
        .level-0 { padding-left: 0px; }
        .level-1 { padding-left: 15px; border-left: 3px solid #eee; }
        .level-2 { padding-left: 30px; border-left: 3px solid #ccc; }
        .level-3 { padding-left: 45px; border-left: 3px solid #999; }

        .tag { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 10px; margin-right: 3px; margin-bottom: 2px; border: 1px solid #ccc; font-weight: 500; }
        .tag-hw { background: #e0f2ff; border-color: #b8daff; color: #004085; }
        .tag-resp { background: #e0ffef; border-color: #b3e6ce; color: #0f5132; }
        .tag-store { background: #fff3cd; border-color: #ffeeba; color: #856404; }
        .tag-maint { background: #f0e6ff; border-color: #d6b8ff; color: #5a2d82; }

        .section-label { font-size: 9px; text-transform: uppercase; color: #999; font-weight: bold; margin-bottom: 2px; margin-top: 6px; }
        .section-label:first-child { margin-top: 0; }

        .flow-io { font-size: 11px; margin-bottom: 4px; }
        .flow-label { font-weight: bold; color: #555; }
        .flow-in { color: #28a745; } 
        .flow-out { color: #dc3545; } 

        .page-break { page-break-before: always; }

        @media print { 
            .no-print { display: none; } 
            body { padding: 0; } 
            th { background-color: #eee !important; -webkit-print-color-adjust: exact; } 
            .tag { -webkit-print-color-adjust: exact; }
        }
    </style>
</head>
<body>
    <div class="no-print" style="margin-bottom: 20px;">
        <button onclick="window.print()" style="padding: 10px 20px; cursor: pointer; font-size: 14px; background: #0055A4; color: white; border: none; border-radius: 4px;">🖨️ Bericht drucken</button>
    </div>

    <h1>Anlagen-Strukturbericht</h1>
    <div class="meta-info">Erstellt am: {{ date }} | MBET Facility Management</div>

    <!-- TEIL 1: BETRIEBSEINHEITEN -->
    <h2>1. Betriebseinheiten ({{ count_units }} Elemente)</h2>
    <table>
        <thead>
            <tr>
                <th style="width: 40px;">Idx</th>
                <th style="width: 250px;">Bezeichnung / Hierarchie</th>
                <th>Verantwortlichkeit & Assets</th>
                <th>Prozessflüsse</th>
                <th>Details</th>
            </tr>
        </thead>
        <tbody>
            {% for item in flat_data_units %}
            <tr>
                <td>{{ item.index_str }}</td>
                <td><div class="level-{{ item.level if item.level < 4 else 3 }}"><span class="node-name">{{ item.name }}</span></div></td>
                <td>
                    {% if item.bereich or item.verantwortlicher %}
                        <div class="section-label">Verantwortlich:</div>
                        {% if item.bereich %}<span class="tag tag-resp">{{ item.bereich }}</span>{% endif %}
                        {% if item.verantwortlicher %}<span class="tag tag-resp" style="background:#d4edda;">{{ item.verantwortlicher }}</span>{% endif %}
                    {% endif %}

                    {% if item.hardware %}
                        <div class="section-label">Hardware:</div>
                        {% for hw in item.hardware %}<span class="tag tag-hw">{{ hw }}</span>{% endfor %}
                    {% endif %}

                    {% if item.storageLink %}
                        <div class="section-label">Lager:</div>
                        <span class="tag tag-store">📦 {{ item.storageLink }}</span>
                    {% endif %}

                    {% if item.maintenance_plans %}
                        <div class="section-label">Wartungspläne (aktiv):</div>
                        {% for plan in item.maintenance_plans %}
                            <div style="margin-bottom:3px;">
                                <span class="tag tag-maint">🔧 [{{ plan.id }}] {{ plan.task }}</span>
                                <span style="font-size:10px; color:#666;">{{ plan.topic }} · {{ plan.intervalValue }}× / {{ plan.intervalUnit }}</span>
                            </div>
                        {% endfor %}
                    {% endif %}
                </td>
                <td>
                    {% for flow in item.flows_in %}<div class="flow-io"><span class="flow-label flow-in">⬇ IN:</span> {{ flow.name }} ({{ flow.medium }})</div>{% endfor %}
                    {% for flow in item.flows_out %}<div class="flow-io"><span class="flow-label flow-out">⬆ OUT:</span> {{ flow.name }} ({{ flow.medium }})</div>{% endfor %}
                    {% for flow in item.flows_assoc %}<div class="flow-io"><span class="flow-label">● LINK:</span> {{ flow.name }}</div>{% endfor %}
                </td>
                <td>{% for k, v in item.details.items() %}<div><strong>{{ k }}:</strong> {{ v }}</div>{% endfor %}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    <div class="page-break"></div>

    <!-- TEIL 2: GEBÄUDE -->
    <h2>2. Gebäudestruktur ({{ count_buildings }} Elemente)</h2>
    <table>
        <thead>
            <tr>
                <th style="width: 40px;">Idx</th>
                <th style="width: 250px;">Bezeichnung / Hierarchie</th>
                <th>Verantwortlichkeit & Ausstattung</th>
                <th>Prozessflüsse</th>
                <th>Details</th>
            </tr>
        </thead>
        <tbody>
            {% for item in flat_data_buildings %}
            <tr>
                <td>{{ item.index_str }}</td>
                <td><div class="level-{{ item.level if item.level < 4 else 3 }}"><span class="node-name">{{ item.name }}</span></div></td>
                <td>
                    {% if item.bereich or item.verantwortlicher %}
                        <div class="section-label">Verantwortlich:</div>
                        {% if item.bereich %}<span class="tag tag-resp">{{ item.bereich }}</span>{% endif %}
                        {% if item.verantwortlicher %}<span class="tag tag-resp" style="background:#d4edda;">{{ item.verantwortlicher }}</span>{% endif %}
                    {% endif %}

                    {% if item.hardware %}
                        <div class="section-label">Ausstattung:</div>
                        {% for hw in item.hardware %}<span class="tag tag-hw">{{ hw }}</span>{% endfor %}
                    {% endif %}

                    {% if item.storageLink %}
                        <div class="section-label">Lager:</div>
                        <span class="tag tag-store">📦 {{ item.storageLink }}</span>
                    {% endif %}

                    {% if item.maintenance_plans %}
                        <div class="section-label">Wartungspläne (aktiv):</div>
                        {% for plan in item.maintenance_plans %}
                            <div style="margin-bottom:3px;">
                                <span class="tag tag-maint">🔧 [{{ plan.id }}] {{ plan.task }}</span>
                                <span style="font-size:10px; color:#666;">{{ plan.topic }} · {{ plan.intervalValue }}× / {{ plan.intervalUnit }}</span>
                            </div>
                        {% endfor %}
                    {% endif %}
                </td>
                <td>
                    {% for flow in item.flows_in %}<div class="flow-io"><span class="flow-label flow-in">⬇ IN:</span> {{ flow.name }} ({{ flow.medium }})</div>{% endfor %}
                    {% for flow in item.flows_out %}<div class="flow-io"><span class="flow-label flow-out">⬆ OUT:</span> {{ flow.name }} ({{ flow.medium }})</div>{% endfor %}
                    {% for flow in item.flows_assoc %}<div class="flow-io"><span class="flow-label">● LINK:</span> {{ flow.name }}</div>{% endfor %}
                </td>
                <td>{% for k, v in item.details.items() %}<div><strong>{{ k }}:</strong> {{ v }}</div>{% endfor %}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <div class="page-break"></div>

<!-- TEIL 3: RÜSTWAGEN -->
    <h2>3. Mobile Rüstwagen ({{ trolleys|length }} Einheiten)</h2>
    {% if trolleys|length > 0 %}
        {% for name, data in trolleys.items() %}
        <div style="margin-bottom: 25px; page-break-inside: avoid;">
            <h3 style="margin: 0 0 5px 0; font-size: 14px; border-bottom: 1px solid #ccc; padding-bottom: 5px;">
                🛒 {{ name }} <span style="font-weight: normal; font-size: 11px; color: #666; margin-left: 10px;">(Engine Type: {{ data.engineType }})</span>{% if data.responsible %}<span style="font-weight: normal; font-size: 11px; background:#d4edda; border-radius:3px; padding:1px 6px; margin-left:8px;">Verantwortlich: {{ data.responsible }}</span>{% endif %}
            </h3>
            
            <!-- HIER WAR DER FEHLER: data['items'] statt data.items -->
            {% if data['items']|length > 0 %}
            <table style="margin-top: 5px; font-size: 11px;">
                <thead>
                    <tr>
                        <th style="width: 100px;">P/N</th>
                        <th style="width: 100px;">S/N</th>
                        <th>Bezeichnung / Name</th>
                        <th style="width: 40px; text-align: center;">Menge</th>
                        <th style="width: 60px;">Pos (L/B)</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- HIER EBENFALLS: data['items'] -->
                    {% for item in data['items'] %}
                    <tr>
                        <td style="font-family: monospace;">{{ item.pn }}</td>
                        <td style="font-family: monospace;">{{ item.sn }}</td>
                        <td>{{ item.name or 'Unbekannt' }}</td>
                        <td style="text-align: center;">{{ item.qty }}</td>
                        <td>{{ item.level }}/{{ item.box }}</td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
            {% else %}
            <div style="font-style: italic; color: #999; font-size: 11px; padding: 5px;">Leer / Keine Items.</div>
            {% endif %}
        </div>
        {% endfor %}
    {% else %}
        <p>Keine Rüstwagen definiert.</p>
    {% endif %}

    <div class="page-break"></div>

    <!-- TEIL 4: LAGERORTE -->
    <h2>4. Feste Lagerorte ({{ storage|length }} Einheiten)</h2>
    {% if storage|length > 0 %}
        {% for name, data in storage.items() %}
        <div style="margin-bottom: 25px; page-break-inside: avoid;">
            <h3 style="margin: 0 0 5px 0; font-size: 14px; border-bottom: 1px solid #ccc; padding-bottom: 5px;">
                📦 {{ name }} <span style="font-weight: normal; font-size: 11px; color: #666; margin-left: 10px;">(Bereich: {{ data.engineType }})</span>{% if data.responsible %}<span style="font-weight: normal; font-size: 11px; background:#d4edda; border-radius:3px; padding:1px 6px; margin-left:8px;">Verantwortlich: {{ data.responsible }}</span>{% endif %}
            </h3>
            
            <!-- HIER WAR DER FEHLER: data['items'] statt data.items -->
            {% if data['items']|length > 0 %}
            <table style="margin-top: 5px; font-size: 11px;">
                <thead>
                    <tr>
                        <th style="width: 100px;">P/N</th>
                        <th style="width: 100px;">S/N</th>
                        <th>Bezeichnung / Name</th>
                        <th style="width: 40px; text-align: center;">Menge</th>
                        <th style="width: 60px;">Pos (L/B)</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- HIER EBENFALLS: data['items'] -->
                    {% for item in data['items'] %}
                    <tr>
                        <td style="font-family: monospace;">{{ item.pn }}</td>
                        <td style="font-family: monospace;">{{ item.sn }}</td>
                        <td>{{ item.name or 'Unbekannt' }}</td>
                        <td style="text-align: center;">{{ item.qty }}</td>
                        <td>{{ item.level }}/{{ item.box }}</td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
            {% else %}
            <div style="font-style: italic; color: #999; font-size: 11px; padding: 5px;">Leer / Keine Items.</div>
            {% endif %}
        </div>
        {% endfor %}
    {% else %}
        <p>Keine Lagerorte definiert.</p>
    {% endif %}
</body>
</html>
"""

FACILITY_VIEWER_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interaktive Anlagen-Visualisierung</title>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <style>
        :root { --panel-width: 500px; --panel-top-margin: 20px; --panel-left-margin: 20px; --button-size: 44px; }
        body { margin: 0; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: #f4f6f8; }

        #webgl-canvas-container, #css-label-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; }
        #css-label-container { pointer-events: none; }
        body.picking-mode, body.picking-mode #webgl-canvas-container { cursor: crosshair !important; }

        /* STATUS LABELS */
        .status-label { background-color: rgba(255, 255, 255, 0.9); color: #333; padding: 5px 12px; border-radius: 8px; font-size: 13px; font-weight: 500; backdrop-filter: blur(10px); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.05); pointer-events: auto; cursor: pointer; transition: all 0.2s ease; text-align: left; }
        .status-label:hover { transform: scale(1.05) translateY(-2px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); }
        .status-label.expanded { min-width: 180px; padding: 10px 15px; }
        .status-label hr { border: none; border-top: 1px solid #eee; margin: 8px 0; }
        .status-label .detail-item { display: flex; justify-content: space-between; font-size: 12px; margin-top: 4px; }
        .status-label .detail-item strong { color: #555; font-weight: 600; }

        /* SIDEBAR CONTAINER */
        #treeview-container { position: fixed; top: 20px; left: 74px; width: var(--panel-width); max-height: calc(100vh - 40px); background-color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); padding: 0 15px 15px 15px; box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; z-index: 20; opacity: 0; transform: translateX(-20px); transition: opacity 0.3s ease, transform 0.3s ease; pointer-events: none; transition: width 0.3s ease, opacity 0.3s ease, transform 0.3s ease; }
        #treeview-container.visible { opacity: 1; transform: translateX(0); pointer-events: auto; }

        #toggle-treeview-btn { position: fixed; top: 20px; left: 20px; width: 44px; height: 44px; background-color: #fff; border: 1px solid #ddd; border-radius: 8px; cursor: pointer; z-index: 30; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); font-size: 24px; line-height: 1; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; }
        #toggle-treeview-btn:hover { background-color: #f8f8f8; transform: translateY(-2px); }

        /* HEADER */
        .treeview-header { position: relative; display: flex; justify-content: space-between; align-items: center; padding: 15px 0 10px 0; flex-shrink: 0; border-bottom: 1px solid transparent; transition: border-color 0.2s; }
        .treeview-header.with-border { border-bottom: 1px solid #eee; margin-bottom: 10px; }
        .header-title-group { display: flex; align-items: center; gap: 10px; flex-shrink: 0; flex-grow: 1; overflow: hidden; }
        .treeview-header h3 { margin: 0; font-size: 18px; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .header-actions { display: flex; gap: 6px; flex-shrink: 0; margin-left: 10px; align-items: center; }
        .header-btn-round { background: #e9ecef; border: 1px solid #dee2e6; color: #495057; border-radius: 50%; cursor: pointer; width: 30px; height: 30px; font-size: 14px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; padding: 0; flex-shrink: 0; }
        .header-btn-round:hover { background: #ced4da; border-color: #adb5bd; color: #212529; }
        .header-btn-round.active { background: #007bff; color: white; border-color: #0056b3; }
        .action-icon-text { font-size: 18px; color: #666; cursor: pointer; padding: 0 4px; display: inline-block; line-height: 1; }
        .action-icon-text:hover { color: #000; }
        #save-indicator { font-size: 11px; color: #28a745; opacity: 0; transition: opacity 0.5s ease; pointer-events: none; margin-right: 10px; white-space: nowrap; }

        .sidebar-view { display: flex; flex-direction: column; height: 100%; overflow: hidden; flex: 1; }
        .sidebar-view.hidden { display: none !important; }

        #search-container { margin-bottom: 10px; flex-shrink: 0; background: #f8f9fa; padding: 10px; border-radius: 8px; border: 1px solid #eee; display: none; }
        #search-container.visible { display: block; animation: fadeIn 0.2s ease-in-out; }
        @keyframes fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } }
        #search-input { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box; transition: border-color 0.2s; background: #fff; }
        #search-input:focus { border-color: #007bff; outline: none; }
        .search-filters { display: flex; gap: 5px; margin-top: 5px; }
        .filter-btn { border: 1px solid #ddd; background: #fff; font-size: 11px; padding: 2px 8px; border-radius: 10px; cursor: pointer; color: #666; transition: all 0.1s; }
        .filter-btn:hover { background: #eee; }
        .filter-btn.active { background: #007bff; color: white; border-color: #007bff; }

        #tree { flex-grow: 1; overflow-y: auto; padding-right: 5px; margin-top: 5px; border-top: 1px solid #eee; padding-top: 10px; }
        .treeview ul { list-style-type: none; padding-left: 17px; margin: 0; border-left: 1px solid #e8e8e8; }
        #tree > ul { border-left: none; padding-left: 0; }
        #tree > ul > li > ul { border-left-color: #dddddd; }
        .treeview li { padding: 1px 0; }

        .tree-row { display: flex; align-items: center; padding: 5px 8px; border-radius: 6px; transition: background-color 0.2s ease; position: relative; font-size: 14px; cursor: pointer; border: 2px solid transparent; }
        .tree-row:hover { background-color: #f0f0f0; } 
        .tree-row.active { background-color: #e0eaf3; }
        .tree-row.search-match { background-color: #fff8e1; border-color: #ffe58f; }

        .tree-row.drag-over-middle { background-color: #e6f2ff; border: 2px solid #007bff; }
        .tree-row.drag-over-top { border-top: 2px solid #007bff; }
        .tree-row.drag-over-bottom { border-bottom: 2px solid #007bff; }

        #tree.drag-sorting .tree-row { cursor: grab; }
        #tree.drag-sorting .tree-row:active { cursor: grabbing; }
        .tree-drag-handle { display: none; font-size: 16px; color: #adb5bd; margin-right: 4px; flex-shrink: 0; line-height: 1; }
        #tree.drag-sorting .tree-drag-handle { display: inline-flex; align-items: center; }

        .tree-toggler { cursor: pointer; width: 18px; text-align: center; z-index: 5; }
        .tree-toggler::before { content: '▶'; font-size: 0.7em; transition: transform 0.2s ease; display: inline-block; }
        .tree-item.open > .tree-row > .tree-toggler::before { transform: rotate(90deg); }
        .tree-toggler.empty { cursor: default; } .tree-toggler.empty::before { content: ''; }
        .tree-item > ul { display: none; }
        .tree-item.open > ul { display: block; }
        .tree-number { width: 45px; flex-shrink: 0; text-align: right; margin-right: 8px; color: #888; font-size: 13px; }
        .tree-label { flex-grow: 1; user-select: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .tree-leaf .tree-label { font-weight: 500; }
        .tree-row.active .tree-label, .tree-row.active .tree-number { color: #0056b3; }

        .tree-actions { display: none; margin-left: auto; padding-left: 10px; gap: 4px; z-index: 5; align-items: center; }
        #tree.editing .tree-row:hover .tree-actions { display: flex; }

        .info-icon { display: inline-flex; justify-content: center; align-items: center; width: 18px; height: 18px; border-radius: 50%; background-color: #17a2b8; color: white; font-size: 11px; font-weight: bold; font-family: serif; cursor: help; margin-right: 5px; opacity: 0.7; transition: opacity 0.2s; }
        .info-icon:hover { opacity: 1; }
        .tree-row .info-icon-container { margin-left: auto; display: flex; align-items: center; }

        #tree-tooltip { position: fixed; top: 0; left: 0; z-index: 9999; background-color: rgba(30, 30, 30, 0.95); color: #fff; padding: 10px 15px; border-radius: 6px; font-size: 12px; pointer-events: none; display: none; max-width: 300px; box-shadow: 0 5px 20px rgba(0,0,0,0.2); backdrop-filter: blur(4px); line-height: 1.4; }
        #tree-tooltip strong { color: #61bfff; display: block; margin-bottom: 2px; margin-top: 6px; }
        #tree-tooltip strong:first-child { margin-top: 0; }
        #tree-tooltip ul { margin: 0; padding-left: 15px; list-style-type: disc; }
        #tree-tooltip li { margin-bottom: 2px; }

        .sidebar-form-group { margin-bottom: 12px; }
        .sidebar-form-group label { display: block; font-weight: 600; font-size: 13px; color: #555; margin-bottom: 4px; }
        .sidebar-form-group input[type="text"], .sidebar-form-group select { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
        .sidebar-form-group input[type="color"] { width: 100%; padding: 2px; height: 36px; border: 1px solid #ccc; border-radius: 6px; cursor: pointer; box-sizing: border-box; }

        .flow-filter-container { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; background: #f8f9fa; padding: 10px; border-radius: 6px; border: 1px solid #eee; flex-shrink: 0; }
        .flow-filter-container input, .flow-filter-container select { font-size: 13px; padding: 6px; border: 1px solid #ddd; border-radius: 4px; flex: 1; min-width: 100px; box-sizing: border-box; }
        .flow-editor-list-container { flex-grow: 1; overflow-y: auto; padding-right: 2px; }
        .flow-editor-list { list-style: none; padding: 0; margin: 0; }
        .flow-editor-list li { display: flex; justify-content: space-between; align-items: flex-start; padding: 10px; border-bottom: 1px solid #eee; transition: background 0.1s; border-radius: 6px; margin-bottom: 2px; }
        .flow-editor-list li:hover { background-color: #f9f9f9; }
        .flow-editor-list li.active { background-color: #e0eaf3; }
        .flow-editor-list li.active .flow-name strong { color: #0056b3; }
        .flow-editor-list li:last-child { border-bottom: none; }
        .flow-name { cursor: pointer; flex-grow: 1; display: flex; flex-direction: column; gap: 4px; }
        .flow-name strong { color: #333; font-weight: 500; }
        .flow-name:hover strong { color: #007bff; }
        .flow-badges { display: flex; flex-wrap: wrap; gap: 4px; }
        .flow-unit-badge { font-size: 10px; color: #666; background: #e9ecef; padding: 2px 6px; border-radius: 4px; border: 1px solid #dee2e6; }
        .flow-medium-badge { font-size: 10px; color: #004085; background: #cce5ff; padding: 2px 6px; border-radius: 4px; border: 1px solid #b8daff; }
        .flow-actions { display: none; gap: 4px; margin-left: 8px; align-self: center; }
        #view-process-list.editing .flow-actions { display: flex; }
        .add-flow-btn { display: none; width: 100%; padding: 10px; margin-top: 10px; background-color: #28a745; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 15px; flex-shrink: 0; }
        .add-flow-btn:hover { background-color: #218838; }
        #view-process-list.editing .add-flow-btn { display: block; }

        .action-icon { cursor: pointer; font-size: 14px; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: background-color 0.1s; font-weight: normal; }
        .action-icon.add { color: #28a745; } .action-icon.add:hover { background-color: #e6f5e9; }
        .action-icon.delete { color: #dc3545; } .action-icon.delete:hover { background-color: #f8e8e9; }
        .action-icon.edit { color: #007bff; font-size: 16px; } .action-icon.edit:hover { background-color: #e0eaf3; }

        #view-process-edit, #view-structure-edit { overflow-y: auto; padding-right: 5px; }
        #flow-path-builder { display: flex; gap: 5px; align-items: center; }
        #flow-path-segments { list-style-type: none; padding: 0; margin-top: 8px; border: 1px solid #eee; border-radius: 6px; max-height: 150px; overflow-y: auto; background: #fcfcfc; }
        #flow-path-segments li { display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; border-bottom: 1px solid #eee; font-size: 13px; }
        #flow-path-segments li:last-child { border-bottom: none; }
        #flow-path-segments button { background: none; border: none; color: #dc3545; cursor: pointer; font-size: 16px; }
        #flow-path-segments .seg-handle { cursor: grab; color: #bbb; font-size: 15px; margin-right: 5px; flex-shrink: 0; }
        #flow-path-segments .seg-num { font-size: 10px; color: #999; font-weight: bold; min-width: 16px; margin-right: 5px; flex-shrink: 0; }
        #flow-path-segments .seg-label { flex: 1; }
        #flow-path-segments .sortable-ghost { opacity: 0.4; background: #e8f0fe; }

        .sidebar-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 15px; border-top: 1px solid #eee; padding-top: 15px; flex-shrink: 0; }
        .sidebar-footer button { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }
        .sidebar-footer .btn-primary { background-color: #007bff; color: white; }
        .sidebar-footer .btn-secondary { background-color: #f0f0f0; color: #333; }

        #details-editor .detail-row { display: flex; gap: 10px; margin-bottom: 5px; }
        #details-editor .detail-row input { flex: 1; width: 100%; padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; }
        #details-editor .btn-delete-detail { background:none; border:none; color:#dc3545; cursor:pointer; }

        .tags-container { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; min-height: 20px; }
        .mesh-tag { background: #e9ecef; border: 1px solid #dee2e6; padding: 2px 6px; border-radius: 4px; font-size: 12px; display: inline-flex; align-items: center; gap: 5px; }
        .mesh-tag .remove-tag { color: #dc3545; font-weight: bold; cursor: pointer; }
        .hardware-tag { background: #e0f2ff; border: 1px solid #b8daff; color: #004085; padding: 2px 6px; border-radius: 4px; font-size: 12px; display: inline-flex; align-items: center; gap: 5px; }
        .hardware-tag .remove-tag { color: #004085; font-weight: bold; cursor: pointer; opacity: 0.7; }
        .responsibility-tag { background: #e0ffef; border: 1px solid #b3e6ce; color: #0f5132; padding: 2px 6px; border-radius: 4px; font-size: 12px; display: inline-flex; align-items: center; gap: 5px; }
        .responsibility-tag .remove-tag { color: #0f5132; font-weight: bold; cursor: pointer; opacity: 0.7; }
        .remove-tag:hover { opacity: 1; }

        #btn-pick-mesh { padding: 0 12px; border: 1px solid #007bff; background: white; color: #007bff; border-radius: 4px; cursor: pointer; transition: all 0.2s; font-size: 18px; }
        #btn-pick-mesh:hover { background: #e6f2ff; }
        #btn-pick-mesh.active { background: #007bff; color: white; box-shadow: inset 0 2px 4px rgba(0,0,0,0.2); }

        #layers-btn { position: fixed; bottom: 20px; right: 20px; z-index: 25; width: 44px; height: 44px; background: #fff; border: 1px solid #ddd; border-radius: 50%; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; color: #555; }
        #layers-btn:hover { background: #f8f8f8; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }
        #layers-btn.has-custom { color: #007bff; border-color: #007bff; }
        #layers-btn .material-icons { font-size: 22px; }

        #layers-popup { position: fixed; bottom: 74px; right: 20px; z-index: 26; background: rgba(255,255,255,0.97); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); padding: 8px; min-width: 210px; opacity: 0; transform: scale(0.95) translateY(8px); transform-origin: bottom right; transition: opacity 0.15s ease, transform 0.15s ease; pointer-events: none; }
        #layers-popup.open { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; }
        .layers-section-label { font-size: 11px; font-weight: 600; color: #aaa; text-transform: uppercase; letter-spacing: 0.6px; padding: 6px 10px 3px 10px; }
        .layers-option { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px; cursor: pointer; transition: background 0.12s ease; font-size: 14px; color: #333; }
        .layers-option:hover { background: #f4f6f8; }
        .layers-opt-icon { font-size: 18px; color: #aaa; flex-shrink: 0; transition: color 0.12s; }
        .layers-opt-label { flex-grow: 1; }
        .layers-opt-check { font-size: 18px; color: #007bff; margin-left: auto; opacity: 0; transition: opacity 0.12s; flex-shrink: 0; }
        .layers-option.active .layers-opt-check { opacity: 1; }
        .layers-option.active .layers-opt-icon { color: #007bff; }
        .layers-divider { height: 1px; background: #eee; margin: 5px 0; }
        .layers-option.link-action .layers-opt-check { opacity: 1; color: #aaa; }
        .layers-option.link-action:hover .layers-opt-check { color: #007bff; }

        /* EDIT-BUTTON */
        #switch-to-editor-btn { position: fixed; top: 74px; left: 20px; width: 44px; height: 44px; background-color: #333; color: white; border: 1px solid #000; border-radius: 8px; cursor: pointer; z-index: 30; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); font-size: 20px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; text-decoration: none; }
        #switch-to-editor-btn:hover { background-color: #555; transform: translateY(-2px); }

        #active-flow-indicator { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); padding: 6px 16px; border-radius: 20px; border: 1px solid #ccc; font-size: 14px; font-weight: 500; color: #333; display: none; z-index: 50; box-shadow: 0 4px 20px rgba(0,0,0,0.1); pointer-events: auto; display: flex; align-items: center; gap: 8px; }
        #active-flow-indicator span { font-weight: 700; color: #007bff; }
        #active-flow-indicator .medium-tag { font-size: 11px; color: #666; background: #eee; padding: 2px 6px; border-radius: 4px; font-weight: normal; }
        #active-flow-indicator button { background: none; border: none; color: #999; cursor: pointer; font-size: 18px; line-height: 1; padding: 0 4px; transition: color 0.2s; }
        #active-flow-indicator button:hover { color: #333; }
        #close-flow-btn:hover { color: #dc3545 !important; }

        .hidden { display: none !important; }

        /* ZURÜCK-BUTTON für den Viewer */
        .back-home-btn { 
            position: fixed; bottom: 20px; left: 20px; 
            background: rgba(255,255,255,0.8); padding: 10px; border-radius: 50%; 
            box-shadow: 0 2px 10px rgba(0,0,0,0.1); cursor: pointer; transition: all 0.3s; z-index: 60; 
            text-decoration: none; display: flex; justify-content: center; align-items: center;
        }
        .back-home-btn:hover { background: #fff; transform: scale(1.1); }

        #switch-to-editor-btn { 
            position: fixed; 
            top: 74px; /* Unter dem Burger-Menü */
            left: 20px; 
            width: 44px; 
            height: 44px; 
            background-color: #fff; /* Weiß statt Schwarz */
            color: #555; /* Dunkelgraues Icon */
            border: 1px solid #ddd; 
            border-radius: 8px; 
            cursor: pointer; 
            z-index: 30; 
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); 
            display: flex; 
            align-items: center; 
            justify-content: center; 
            transition: all 0.2s ease; 
            text-decoration: none; 
        }
        #switch-to-editor-btn:hover {
            background-color: #f8f8f8;
            transform: translateY(-2px);
            color: #007bff;
        }

        /* WIKI ARROW */
        .wiki-arrow { opacity: 0; transition: opacity 0.15s, color 0.15s, transform 0.2s; color: #bbb; cursor: pointer; font-size: 16px; flex-shrink: 0; padding: 2px 4px; border-radius: 4px; line-height: 1; margin-left: auto; }
        .tree-row:hover .wiki-arrow { opacity: 0.5; }
        .wiki-arrow.has-content { opacity: 1 !important; color: #007bff; }
        .wiki-arrow:hover { background: #e8f0fe; color: #007bff !important; opacity: 1 !important; }
        .wiki-arrow.wiki-open { transform: rotate(180deg); opacity: 1 !important; color: #007bff; }

        /* WIKI PANEL */
        #wiki-panel { position: fixed; top: 20px; left: calc(74px + var(--panel-width) + 10px); width: 420px; max-height: calc(100vh - 40px); background: rgba(255,255,255,0.97); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); display: flex; flex-direction: column; z-index: 20; opacity: 0; transform: translateX(-12px); transition: opacity 0.2s ease, transform 0.2s ease, width 0.3s ease, left 0.3s ease; pointer-events: none; }
        #wiki-panel.visible { opacity: 1; transform: translateX(0); pointer-events: auto; }
        #wiki-panel.expanded { width: calc(100vw - 74px - var(--panel-width) - 30px); }
        #wiki-panel.focus { left: 84px; width: calc(100vw - 104px); }
        #treeview-container.wiki-focus-hidden { opacity: 0 !important; pointer-events: none !important; transform: translateX(-20px) !important; }
        #wiki-expand-btn { background: none; border: none; color: #aaa; cursor: pointer; padding: 2px; transition: color 0.15s; display: flex; align-items: center; }
        #wiki-expand-btn:hover { color: #007bff; }
        #wiki-expand-btn .material-icons { font-size: 20px; }
        #wiki-panel-header { display: flex; align-items: center; gap: 8px; padding: 13px 15px 10px 15px; border-bottom: 1px solid #eee; flex-shrink: 0; }
        #wiki-panel-header h3 { margin: 0; font-size: 15px; color: #333; flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        #wiki-panel-close { background: none; border: none; color: #aaa; cursor: pointer; font-size: 20px; line-height: 1; padding: 0 2px; transition: color 0.15s; }
        #wiki-panel-close:hover { color: #333; }

        /* META ROW (Status + Bereich + Verantwortlicher) */
        #wiki-meta-row { display: flex; flex-direction: column; gap: 6px; padding: 8px 15px; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
        #wiki-meta-row-top { display: flex; gap: 8px; align-items: center; }
        #wiki-status { border: 1px solid #e0e0e0; border-radius: 6px; padding: 5px 8px; font-size: 12px; outline: none; cursor: pointer; background: #fff; flex-shrink: 0; }
        #wiki-status.status-aktuell { color: #28a745; border-color: #c3e6cb; background: #f0fff4; }
        #wiki-status.status-in_bearbeitung { color: #856404; border-color: #ffeaa7; background: #fffdf0; }
        #wiki-status.status-veraltet { color: #dc3545; border-color: #f5c6cb; background: #fff5f5; }
        #wiki-bereich { flex: 1; border: 1px solid #e0e0e0; border-radius: 6px; padding: 5px 8px; font-size: 12px; outline: none; cursor: pointer; background: #fff; min-width: 0; }
        #wiki-bereich:focus { border-color: #007bff; }
        #wiki-verantwortlicher { width: 100%; box-sizing: border-box; border: 1px solid #e0e0e0; border-radius: 6px; padding: 5px 9px; font-size: 13px; outline: none; }
        #wiki-verantwortlicher:focus { border-color: #007bff; }

        /* BODY */
        #wiki-panel-body { padding: 12px 15px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 14px; }
        .wiki-section-label { font-size: 11px; font-weight: 600; color: #aaa; text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 5px; }

        /* TEMPLATE ROW */
        #wiki-template-row { display: flex; align-items: center; gap: 8px; }
        #wiki-template-select { flex: 1; border: 1px solid #e0e0e0; border-radius: 6px; padding: 5px 8px; font-size: 13px; outline: none; background: #fff; }

        /* EDIT BUTTON (header) */
        #wiki-edit-btn { background: none; border: 1px solid #e0e0e0; border-radius: 6px; color: #007bff; cursor: pointer; padding: 3px 5px; display: flex; align-items: center; transition: all 0.15s; flex-shrink: 0; }
        #wiki-edit-btn:hover { background: #f0f6ff; border-color: #007bff; }
        #wiki-edit-btn .material-icons { font-size: 15px; }

        /* VIEW META ROW */
        #wiki-view-meta { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 15px; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; align-items: center; }
        .wiki-view-status-badge { font-size: 12px; padding: 3px 10px; border-radius: 20px; border: 1px solid; font-weight: 500; }
        .wiki-view-status-badge.status-aktuell { color: #28a745; border-color: #c3e6cb; background: #f0fff4; }
        .wiki-view-status-badge.status-in_bearbeitung { color: #856404; border-color: #ffeaa7; background: #fffdf0; }
        .wiki-view-status-badge.status-veraltet { color: #dc3545; border-color: #f5c6cb; background: #fff5f5; }
        .wiki-view-badge { font-size: 12px; padding: 3px 10px; border-radius: 20px; background: #f4f6f8; border: 1px solid #e0e0e0; color: #555; }

        /* VIEW LINKS + PRUEFUNG */
        #wiki-view-links { display: flex; flex-direction: column; gap: 5px; }
        .wiki-view-link { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: #007bff; text-decoration: none; }
        .wiki-view-link:hover { text-decoration: underline; }


        /* TEXTAREA + PREVIEW */
        #wiki-notizen { width: 100%; min-height: 140px; border: 1px solid #e0e0e0; border-radius: 8px; padding: 10px; font-size: 13px; font-family: 'Courier New', monospace; resize: vertical; box-sizing: border-box; outline: none; color: #333; line-height: 1.6; }
        #wiki-notizen:focus { border-color: #007bff; }
        #wiki-preview { display: none; min-height: 60px; overflow-y: auto; font-size: 13px; line-height: 1.7; color: #333; }
        #wiki-preview h1 { font-size: 16px; font-weight: 700; border-bottom: 1px solid #eee; padding-bottom: 5px; margin: 4px 0 10px; }
        #wiki-preview h2 { font-size: 14px; font-weight: 600; margin: 12px 0 6px; color: #222; }
        #wiki-preview h3 { font-size: 13px; font-weight: 600; margin: 10px 0 4px; color: #444; }
        #wiki-preview ul, #wiki-preview ol { padding-left: 20px; margin: 4px 0; }
        #wiki-preview li { margin: 2px 0; }
        #wiki-preview table { border-collapse: collapse; width: 100%; font-size: 12px; margin: 8px 0; }
        #wiki-preview th, #wiki-preview td { border: 1px solid #ddd; padding: 5px 9px; text-align: left; }
        #wiki-preview th { background: #f4f6f8; font-weight: 600; }
        #wiki-preview code { background: #f4f6f8; padding: 1px 5px; border-radius: 3px; font-size: 12px; font-family: monospace; }
        #wiki-preview pre { background: #f4f6f8; padding: 10px; border-radius: 6px; overflow-x: auto; }
        #wiki-preview blockquote { border-left: 3px solid #007bff; padding-left: 10px; color: #666; margin: 6px 0; }
        #wiki-preview a { color: #007bff; text-decoration: none; }
        #wiki-preview p { margin: 4px 0 8px; }

        /* LINKS */
        .wiki-link-row { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
        .wiki-link-row input { flex: 1; border: 1px solid #e0e0e0; border-radius: 6px; padding: 6px 9px; font-size: 13px; outline: none; transition: border-color 0.15s; min-width: 0; }
        .wiki-link-row input:focus { border-color: #007bff; }
        .wiki-link-row .wiki-link-open { color: #007bff; cursor: pointer; font-size: 16px; flex-shrink: 0; padding: 0 2px; opacity: 0.7; transition: opacity 0.15s; }
        .wiki-link-row .wiki-link-open:hover { opacity: 1; }
        .wiki-link-row .wiki-link-del { color: #ccc; cursor: pointer; font-size: 18px; line-height: 1; flex-shrink: 0; transition: color 0.15s; }
        .wiki-link-row .wiki-link-del:hover { color: #dc3545; }
        #wiki-add-link-btn { background: none; border: 1px dashed #ccc; border-radius: 6px; color: #999; font-size: 13px; cursor: pointer; padding: 6px 12px; width: 100%; transition: all 0.15s; }
        #wiki-add-link-btn:hover { border-color: #007bff; color: #007bff; background: #f0f6ff; }



        /* FOOTER */
        .wiki-panel-footer { display: flex; align-items: center; gap: 8px; padding: 10px 15px; border-top: 1px solid #eee; flex-shrink: 0; }
        #wiki-audit-info { font-size: 11px; color: #bbb; line-height: 1.5; flex: 1; }
        #wiki-save-indicator { font-size: 12px; color: #28a745; opacity: 0; transition: opacity 0.4s; white-space: nowrap; }
        #wiki-save-btn { background: #007bff; color: white; border: none; border-radius: 8px; padding: 8px 18px; font-size: 13px; font-weight: 500; cursor: pointer; transition: background 0.15s; flex-shrink: 0; }
        #wiki-save-btn:hover { background: #0056b3; }
        #wiki-history-btn { background: none; border: none; color: #aaa; cursor: pointer; padding: 2px; transition: color 0.15s; display: flex; align-items: center; }
        #wiki-history-btn:hover { color: #007bff; }
        #wiki-history-btn .material-icons { font-size: 20px; }

        /* HISTORY OVERLAY */
        #wiki-history-overlay { position: absolute; inset: 0; background: rgba(255,255,255,0.98); border-radius: 12px; display: flex; flex-direction: column; z-index: 10; opacity: 0; pointer-events: none; transition: opacity 0.15s; }
        #wiki-history-overlay.visible { opacity: 1; pointer-events: auto; }
        #wiki-history-header { display: flex; align-items: center; padding: 13px 15px 10px 15px; border-bottom: 1px solid #eee; flex-shrink: 0; gap: 8px; }
        #wiki-history-header span { font-size: 14px; font-weight: 600; color: #333; flex-grow: 1; }
        #wiki-history-close { background: none; border: none; color: #aaa; cursor: pointer; font-size: 20px; line-height: 1; padding: 0; transition: color 0.15s; }
        #wiki-history-close:hover { color: #333; }
        #wiki-history-list { overflow-y: auto; flex: 1; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
        .wiki-history-entry { border: 1px solid #eee; border-radius: 8px; padding: 10px 12px; cursor: default; }
        .wiki-history-entry-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
        .wiki-history-entry-date { font-size: 12px; font-weight: 600; color: #555; }
        .wiki-history-entry-user { font-size: 12px; color: #999; flex: 1; }
        .wiki-history-entry-status { font-size: 11px; padding: 1px 6px; border-radius: 10px; background: #f0f0f0; color: #666; }
        .wiki-history-entry-preview { font-size: 12px; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px; font-family: monospace; }
        .wiki-history-restore-btn { background: #f4f6f8; border: 1px solid #e0e0e0; border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; color: #555; transition: all 0.15s; width: 100%; }
        .wiki-history-restore-btn:hover { background: #e8f0fe; border-color: #007bff; color: #007bff; }

        /* WIKI SEARCH BUTTON */
        #wiki-search-btn { position: fixed; bottom: 74px; right: 20px; z-index: 25; width: 44px; height: 44px; background: #fff; border: 1px solid #ddd; border-radius: 50%; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; color: #555; }
        #wiki-search-btn:hover { background: #f8f8f8; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }
        #wiki-search-btn .material-icons { font-size: 22px; }

        /* WIKI SEARCH POPUP */
        #wiki-search-popup { position: fixed; bottom: 128px; right: 20px; z-index: 26; background: rgba(255,255,255,0.97); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); width: 320px; opacity: 0; transform: scale(0.95) translateY(8px); transform-origin: bottom right; transition: opacity 0.15s ease, transform 0.15s ease; pointer-events: none; overflow: hidden; }
        #wiki-search-popup.open { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; }
        #wiki-search-input-wrap { padding: 10px 12px; border-bottom: 1px solid #eee; display: flex; align-items: center; gap: 8px; }
        #wiki-search-input-wrap .material-icons { color: #aaa; font-size: 20px; flex-shrink: 0; }
        #wiki-search-input { border: none; outline: none; font-size: 14px; flex: 1; background: transparent; }
        #wiki-search-results { max-height: 280px; overflow-y: auto; }
        .wiki-search-result { padding: 10px 14px; cursor: pointer; border-bottom: 1px solid #f4f4f4; transition: background 0.1s; }
        .wiki-search-result:last-child { border-bottom: none; }
        .wiki-search-result:hover { background: #f4f6f8; }
        .wiki-search-result-name { font-size: 13px; font-weight: 600; color: #333; margin-bottom: 2px; }
        .wiki-search-result-excerpt { font-size: 12px; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .wiki-search-result mark { background: #fff3b0; border-radius: 2px; padding: 0 1px; font-style: normal; }
        #wiki-search-empty { padding: 20px; text-align: center; font-size: 13px; color: #bbb; }
        .wiki-search-type-badge { display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 10px; font-size: 10px; font-weight: 600; vertical-align: middle; }
        .wiki-search-type-storage { background: #e8f5e9; color: #2e7d32; }

        /* WIKI HEADER STATUS BADGE */
        #wiki-header-status-badge { display: none; width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; margin-left: 2px; align-self: center; }
        #wiki-header-status-badge.level-3 { background: #dc3545; display: inline-block; box-shadow: 0 0 0 2px rgba(220,53,69,0.25); }
        #wiki-header-status-badge.level-2 { background: #fd7e14; display: inline-block; box-shadow: 0 0 0 2px rgba(253,126,20,0.25); }
        #wiki-header-status-badge.level-1 { background: #007bff; display: inline-block; box-shadow: 0 0 0 2px rgba(0,123,255,0.2); }

        /* WIKI INFO SECTIONS (Wartung + Lager) */
        .wiki-info-section { border: 1px solid #eee; border-radius: 8px; overflow: hidden; }
        .wiki-info-section-header { display: flex; align-items: center; gap: 7px; padding: 8px 10px; cursor: pointer; background: #f8f9fa; transition: background 0.1s; user-select: none; }
        .wiki-info-section-header:hover { background: #f0f2f5; }
        .wiki-info-icon { font-size: 16px; color: #666; flex-shrink: 0; }
        .wiki-info-section-title { font-size: 12px; font-weight: 600; color: #444; flex: 1; }
        .wiki-info-count { font-size: 11px; color: #888; flex-shrink: 0; }
        .wiki-info-chevron { font-size: 18px; color: #aaa; transition: transform 0.2s; flex-shrink: 0; }
        .wiki-info-section.open .wiki-info-chevron { transform: rotate(180deg); }
        .wiki-info-section-body { display: none; border-top: 1px solid #eee; max-height: 190px; overflow-y: auto; }
        .wiki-info-section.open .wiki-info-section-body { display: block; }
        #wiki-info-zones { flex-shrink: 0; display: none; flex-direction: column; gap: 6px; padding: 10px 15px 8px 15px; border-top: 1px solid #f0f0f0; margin-top: 4px; }
        #wiki-info-zones.has-content { display: flex; }
        /* Order rows */
        .wiki-order-row { display: flex; align-items: center; gap: 7px; padding: 7px 10px; border-bottom: 1px solid #f5f5f5; text-decoration: none; transition: background 0.1s; }
        .wiki-order-row:last-child { border-bottom: none; }
        .wiki-order-row:hover { background: #f4f6f8; }
        .wiki-order-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
        .wiki-order-dot.level-3 { background: #dc3545; }
        .wiki-order-dot.level-2 { background: #fd7e14; }
        .wiki-order-dot.level-1 { background: #007bff; }
        .wiki-order-dot.level-0 { background: #aaa; }
        .wiki-order-title { font-size: 12px; color: #333; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .wiki-order-row:hover .wiki-order-title { color: #007bff; }
        .wiki-order-meta { font-size: 11px; color: #aaa; flex-shrink: 0; white-space: nowrap; }
        .wiki-order-type { font-size: 10px; padding: 1px 5px; border-radius: 3px; flex-shrink: 0; font-weight: 600; }
        .wiki-order-type.type-M { background: #e8f4fd; color: #0c78c4; }
        .wiki-order-type.type-I { background: #fff3e0; color: #e65100; }
        /* Storage rows */
        .wiki-storage-item { display: flex; align-items: center; gap: 7px; padding: 6px 10px; border-bottom: 1px solid #f5f5f5; font-size: 12px; }
        .wiki-storage-item:last-child { border-bottom: none; }
        .wiki-storage-item.checked-out { background: #fffbf0; }
        @keyframes wiki-storage-flash { 0%,100%{background:#fff} 30%{background:#fff3b0} }
        .wiki-storage-item.search-highlight { animation: wiki-storage-flash 1.4s ease; }
        .wiki-storage-item-desc { flex: 1; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .wiki-storage-item-pn { font-size: 11px; color: #999; flex-shrink: 0; font-family: monospace; }
        .wiki-storage-item-qty { font-size: 11px; font-weight: 600; color: #555; flex-shrink: 0; min-width: 20px; text-align: right; }
        .wiki-storage-item-co { font-size: 11px; color: #e65100; flex-shrink: 0; }
        .wiki-storage-item-loc { font-size: 10px; color: #bbb; flex-shrink: 0; }

        /* SLASH COMMAND PALETTE */
        #wiki-slash-palette { position: fixed; z-index: 31; background: rgba(255,255,255,0.97); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.1); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.13); width: 260px; max-height: 280px; overflow-y: auto; display: none; }
        .slash-group-label { font-size: 10px; font-weight: 700; color: #aaa; text-transform: uppercase; letter-spacing: 0.7px; padding: 8px 12px 4px; }
        .slash-item { display: flex; align-items: center; gap: 10px; padding: 7px 12px; cursor: pointer; transition: background 0.1s; border-radius: 0; }
        .slash-item:hover, .slash-item.active { background: #e8f0fe; }
        .slash-item .material-icons { font-size: 17px; color: #555; flex-shrink: 0; }
        .slash-item-label { font-size: 13px; color: #333; }
        .slash-item-hint { font-size: 11px; color: #aaa; margin-left: auto; }
        .slash-empty { padding: 14px; font-size: 13px; color: #aaa; text-align: center; }
        /* UPLOAD INDICATOR */
        #wiki-upload-indicator { position: absolute; inset: 0; background: rgba(255,255,255,0.92); border-radius: 8px; display: none; align-items: center; justify-content: center; flex-direction: column; gap: 8px; z-index: 5; }
        #wiki-upload-indicator.visible { display: flex; }
        .upload-spinner { width: 28px; height: 28px; border: 3px solid #e0e0e0; border-top-color: #007bff; border-radius: 50%; animation: spin 0.7s linear infinite; }
        @keyframes spin { to { transform: rotate(360deg); } }
        #wiki-upload-label { font-size: 13px; color: #555; }
        /* DRAG OVER HIGHLIGHT */
        #wiki-notizen.drag-over { border-color: #007bff !important; background: #f0f6ff !important; }
        /* ATTACHMENTS SECTION */
        #wiki-attachments-section { display: flex; flex-direction: column; gap: 0; }
        #wiki-attachments-list { display: flex; flex-direction: column; gap: 4px; }
        .wiki-attach-item { display: flex; align-items: center; gap: 7px; padding: 5px 8px; border-radius: 6px; background: #f8f9fa; border: 1px solid #eee; }
        .wiki-attach-item:hover { background: #f0f4ff; border-color: #c8d8f8; }
        .wiki-attach-icon { font-size: 16px; color: #666; flex-shrink: 0; }
        .wiki-attach-name { font-size: 12px; color: #333; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
        .wiki-attach-name:hover { color: #007bff; text-decoration: underline; }
        .wiki-attach-size { font-size: 11px; color: #aaa; flex-shrink: 0; }
        .wiki-attach-del { color: #ccc; cursor: pointer; font-size: 16px; flex-shrink: 0; transition: color 0.15s; background: none; border: none; padding: 0; line-height: 1; }
        .wiki-attach-del:hover { color: #dc3545; }
        #wiki-attach-upload-btn { background: none; border: 1px dashed #ccc; border-radius: 6px; color: #999; font-size: 13px; cursor: pointer; padding: 6px 12px; width: 100%; transition: all 0.15s; display: none; }
        #wiki-attach-upload-btn:hover { border-color: #007bff; color: #007bff; background: #f0f6ff; }
        /* INLINE IMAGE in preview */
        #wiki-preview img { max-width: 100%; border-radius: 6px; cursor: zoom-in; margin: 4px 0; display: block; }
        #wiki-preview a.attach-link { display: inline-flex; align-items: center; gap: 4px; color: #007bff; font-size: 13px; }
        #wiki-preview a.attach-link .material-icons { font-size: 15px; }
        /* LIGHTBOX */
        #wiki-lightbox { position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 999; display: none; align-items: center; justify-content: center; cursor: zoom-out; }
        #wiki-lightbox.open { display: flex; }
        #wiki-lightbox img { max-width: 90vw; max-height: 90vh; border-radius: 8px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
        #wiki-lightbox-close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 32px; cursor: pointer; line-height: 1; opacity: 0.8; }
        #wiki-lightbox-close:hover { opacity: 1; }

        /* AT-MENTION NODE PICKER */
        #wiki-at-picker { position: fixed; z-index: 30; background: rgba(255,255,255,0.97); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.1); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.13); width: 290px; max-height: 224px; overflow-y: auto; display: none; }
        .wiki-at-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; transition: background 0.1s; border-bottom: 1px solid #f4f4f4; }
        .wiki-at-item:last-child { border-bottom: none; }
        .wiki-at-item:hover, .wiki-at-item.active { background: #e8f0fe; }
        .wiki-at-item-num { font-size: 11px; color: #007bff; font-weight: 600; flex-shrink: 0; min-width: 32px; }
        .wiki-at-item-name { font-size: 13px; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .wiki-at-item mark { background: #fff3b0; border-radius: 2px; padding: 0 1px; font-style: normal; }
        .wiki-at-empty { padding: 14px; font-size: 13px; color: #aaa; text-align: center; }
        /* NODE REF CHIP (view mode) */
        .wiki-node-ref { display: inline-flex; align-items: center; gap: 3px; background: #e8f0fe; color: #1a73e8; border-radius: 4px; padding: 1px 7px 1px 5px; font-size: 0.88em; font-weight: 500; cursor: pointer; white-space: nowrap; transition: background 0.15s; vertical-align: middle; }
        .wiki-node-ref:hover { background: #d2e3fc; }
        .wiki-node-ref .at-icon { font-size: 13px; opacity: 0.7; }
    </style>
</head>
<body>
    <div id="webgl-canvas-container"></div>
    <div id="css-label-container"></div>

    <div id="tree-tooltip"></div>

    <div id="active-flow-indicator" style="display: none;"></div>

    <button id="wiki-search-btn" title="Wiki durchsuchen"><span class="material-icons">manage_search</span></button>
    <div id="wiki-search-popup">
        <div id="wiki-search-input-wrap">
            <span class="material-icons">search</span>
            <input id="wiki-search-input" type="text" placeholder="Wiki durchsuchen…" autocomplete="off">
        </div>
        <div id="wiki-search-results"></div>
    </div>

    <div id="wiki-at-picker"></div>
    <div id="wiki-slash-palette"></div>
    <input type="file" id="wiki-file-input" style="display:none" multiple>
    <div id="wiki-lightbox"><span id="wiki-lightbox-close">×</span><img id="wiki-lightbox-img" src="" alt=""></div>

    <div id="wiki-panel">
        <div id="wiki-history-overlay">
            <div id="wiki-history-header">
                <span class="material-icons" style="color:#007bff; font-size:18px;">history</span>
                <span>Änderungsverlauf</span>
                <button id="wiki-history-close" title="Zurück">×</button>
            </div>
            <div id="wiki-history-list"></div>
        </div>
        <div id="wiki-panel-header">
            <span class="material-icons" style="color:#007bff; font-size:18px; flex-shrink:0;">menu_book</span>
            <h3 id="wiki-panel-title">Wiki</h3>
            <span id="wiki-header-status-badge" title="Aktive Wartungsaufträge"></span>
            <button id="wiki-edit-btn" title="Bearbeiten"><span class="material-icons">edit</span></button>
            <button id="wiki-history-btn" title="Änderungsverlauf"><span class="material-icons">history</span></button>
            <button id="wiki-expand-btn" title="Erweitern"><span class="material-icons">open_in_full</span></button>
            <button id="wiki-panel-close" title="Schließen">×</button>
        </div>
        <div id="wiki-view-meta">
            <span id="wiki-view-status" class="wiki-view-status-badge"></span>
            <span id="wiki-view-bereich" class="wiki-view-badge"></span>
            <span id="wiki-view-verantwortlicher" class="wiki-view-badge"></span>
        </div>
        <div id="wiki-meta-row" style="display:none;">
            <div id="wiki-meta-row-top">
                <select id="wiki-status">
                    <option value="aktuell">● Aktuell</option>
                    <option value="in_bearbeitung">◑ In Bearbeitung</option>
                    <option value="veraltet">○ Veraltet</option>
                </select>
                <select id="wiki-bereich">
                    <option value="">— Bereich —</option>
                    <option value="MBET">MBET</option>
                    <option value="Facility Management">Facility Management</option>
                </select>
            </div>
            <input id="wiki-verantwortlicher" type="text" placeholder="Verantwortlicher Name…">
        </div>
        <div id="wiki-panel-body">
            <div id="wiki-template-row" style="display:none;">
                <span class="wiki-section-label" style="margin:0; white-space:nowrap;">Template:</span>
                <select id="wiki-template-select">
                    <option value="">— wählen —</option>
                    <option value="pruefstand">Prüfstand</option>
                    <option value="halle">Halle / Raum</option>
                    <option value="allgemein">Allgemein</option>
                </select>
            </div>
            <div>
                <textarea id="wiki-notizen" style="display:none;" placeholder="# Überschrift&#10;&#10;**fett**, *kursiv*, - Liste, | Tabelle |"></textarea>
                <div id="wiki-preview"></div>
            </div>
            <div>
                <div class="wiki-section-label">Links & Dokumente</div>
                <div id="wiki-links-container" style="display:none;"></div>
                <div id="wiki-view-links"></div>
                <button id="wiki-add-link-btn" style="display:none;">+ Link hinzufügen</button>
            </div>
            <div id="wiki-attachments-section">
                <div class="wiki-section-label">Anhänge</div>
                <div id="wiki-attachments-list"></div>
                <button id="wiki-attach-upload-btn">📎 Datei hochladen</button>
            </div>

        </div>
        <div id="wiki-info-zones">
            <div id="wiki-orders-section" class="wiki-info-section" style="display:none">
                <div class="wiki-info-section-header" id="wiki-orders-header">
                    <span class="material-icons wiki-info-icon">build</span>
                    <span class="wiki-info-section-title">Wartungsaufträge</span>
                    <span id="wiki-orders-count" class="wiki-info-count"></span>
                    <span class="material-icons wiki-info-chevron">expand_more</span>
                </div>
                <div id="wiki-orders-list" class="wiki-info-section-body"></div>
            </div>
            <div id="wiki-storage-section" class="wiki-info-section" style="display:none">
                <div class="wiki-info-section-header" id="wiki-storage-header">
                    <span class="material-icons wiki-info-icon">inventory_2</span>
                    <span class="wiki-info-section-title">Lager</span>
                    <span id="wiki-storage-name" class="wiki-info-count"></span>
                    <span class="material-icons wiki-info-chevron">expand_more</span>
                </div>
                <div id="wiki-storage-list" class="wiki-info-section-body"></div>
            </div>
        </div>
        <div id="wiki-upload-indicator"><div class="upload-spinner"></div><div id="wiki-upload-label">Wird hochgeladen…</div></div>
        <div class="wiki-panel-footer">
            <div id="wiki-audit-info"></div>
            <span id="wiki-save-indicator" style="display:none;">✓ Gespeichert</span>
            <button id="wiki-save-btn" style="display:none;">Speichern</button>
        </div>
    </div>

    <button id="layers-btn" title="Ebenenansicht"><span class="material-icons">layers</span></button>
    <div id="layers-popup">
        <div class="layers-section-label">Ansicht</div>
        <div class="layers-option active" id="layer-opt-betriebseinheiten">
            <span class="material-icons layers-opt-icon">account_tree</span>
            <span class="layers-opt-label">Betriebseinheiten</span>
            <span class="material-icons layers-opt-check">check</span>
        </div>
        <div class="layers-option" id="layer-opt-gebaeude">
            <span class="material-icons layers-opt-icon">apartment</span>
            <span class="layers-opt-label">Gebäude</span>
            <span class="material-icons layers-opt-check">check</span>
        </div>
        <div class="layers-option" id="layer-opt-prozesse">
            <span class="material-icons layers-opt-icon">share</span>
            <span class="layers-opt-label">Prozessflüsse</span>
            <span class="material-icons layers-opt-check">check</span>
        </div>
        <div id="custom-layers-container"></div>
        {% if session.get('is_admin') or 'PLANNER' in session.get('roles', []) %}
        <div class="layers-option link-action" id="layer-opt-add-new">
            <span class="material-icons layers-opt-icon" style="color:#007bff;">add_circle_outline</span>
            <span class="layers-opt-label" style="color:#007bff;">Neuer Layer...</span>
        </div>
        {% endif %}
        <div class="layers-divider"></div>
        <div class="layers-section-label">Darstellung</div>
        <div class="layers-option active" id="layer-opt-status">
            <span class="material-icons layers-opt-icon">label</span>
            <span class="layers-opt-label">Status-Labels</span>
            <span class="material-icons layers-opt-check">check</span>
        </div>
        <div class="layers-divider"></div>
        <div class="layers-section-label">Berichte</div>
        <div class="layers-option link-action" id="layer-opt-audit">
            <span class="material-icons layers-opt-icon">print</span>
            <span class="layers-opt-label">Anlagenbericht</span>
            <span class="material-icons layers-opt-check">open_in_new</span>
        </div>
    </div>
    <button id="toggle-treeview-btn" title="Panel ein/ausblenden">☰</button>

    <!-- LINK ZURÜCK ZUR VIEW (Mit Initialen) -->
    <a href="{{ url_for('dashboard_page') }}" class="back-home-btn" title="Zurück zum Dashboard">
        <span style="font-weight: 700; font-size: 16px; color: #0055A4;">{{ current_user_initials }}</span>
    </a>

    <!-- LINK ZUM EDITOR -->
    {% if session.get('is_admin') or 'PLANNER' in session.get('roles', []) %}
    <a href="{{ url_for('serve_facility_editor') }}" id="switch-to-editor-btn" title="Zum 3D-Layout-Editor wechseln">
        <span class="material-icons">build</span>
    </a>
    {% endif %}

    <div id="treeview-container">
        <div class="treeview-header" id="panel-header">

            <div id="header-back-action" style="display: none; margin-right: 10px;">
                <span class="header-btn-round" id="header-back-btn" title="Zurück">←</span>
            </div>

            <div class="header-title-group">
                <h3 id="sidebar-title">Betriebseinheiten</h3>
            </div>

            <div id="save-indicator">Gespeichert</div>

            <div class="header-actions">
                <!-- STRUCTURE ACTIONS -->
                <div id="structure-actions" style="display: flex; gap: 6px; align-items: center;">

                    {% if session.get('is_admin') or 'PLANNER' in session.get('roles', []) %}
                    <span id="toggle-structure-edit-mode-btn" class="header-btn-round" title="Struktur bearbeiten">✎</span>
                    <span id="toggle-drag-sort-btn" class="header-btn-round" title="Drag & Drop Sortierung aktivieren" style="display:none;">⇅</span>
                    {% endif %}

                    <span id="toggle-search-btn" class="header-btn-round" title="Suche">🔍</span>

                    <span class="action-icon-text" id="add-root-btn" title="Überpunkt hinzufügen" style="display:none;">+</span>
                    <span class="action-icon-text" id="expand-all-btn" title="Alles ausklappen">⊞</span>
                    <span class="action-icon-text" id="collapse-all-btn" title="Alles einklappen">⊟</span>
                </div>

                <!-- PROCESS ACTIONS -->
                <div id="process-actions" style="display: none; gap: 6px; align-items: center;">
                    {% if session.get('is_admin') or 'PLANNER' in session.get('roles', []) %}
                    <span id="toggle-flow-edit-mode-btn" class="header-btn-round" title="Bearbeitungsmodus">✎</span>
                    {% endif %}
                </div>

                <!-- MODE SWITCH – now handled by the floating layers button -->
                <div id="app-mode-actions" style="display: none;">
                    <span id="tree-view-switch-btn" style="display:none;"></span>
                    <span id="app-mode-switch-btn" style="display:none;"></span>
                </div>
            </div>
        </div>

        <!-- VIEW 1: STRUKTUR -->
        <div id="view-structure" class="sidebar-view">
            <div id="search-container">
                <input type="text" id="search-input" placeholder="Anlage durchsuchen...">
                <div class="search-filters">
                    <span class="filter-btn active" data-filter="all">Alle</span>
                    <span class="filter-btn" data-filter="critical">Kritisch</span>
                    <span class="filter-btn" data-filter="active">Aktiv</span>
                </div>
            </div>
            <div id="tree" class="treeview"></div>
        </div>

        <!-- VIEW 1.5: STRUKTUR EDITOR -->
        <div id="view-structure-edit" class="sidebar-view hidden">
            <div class="sidebar-form-group">
                <label for="struct-edit-name">Name</label>
                <input type="text" id="struct-edit-name">
            </div>
            <div class="sidebar-form-group">
                <label for="struct-edit-label-name">Name in Statuskachel (optional)</label>
                <input type="text" id="struct-edit-label-name" placeholder="Leer = Hauptname">
            </div>
            <div class="sidebar-form-group">
                <label style="display: flex; align-items: center; gap: 8px;">
                    <input type="checkbox" id="struct-edit-show-label" style="width: auto;"> 
                    Statuskachel anzeigen
                </label>
            </div>
            <hr style="border: 0; border-top: 1px solid #eee; margin: 15px 0;">

            <div class="sidebar-form-group">
                <label>Verbundene 3D-Objekte (Meshes)</label>
                <div id="mesh-tags-container" class="tags-container"></div>
                <div style="display:flex; gap:5px; margin-top:5px;">
                    <select id="mesh-dropdown" style="flex-grow:1"></select>
                    <button id="btn-pick-mesh" title="Objekt aus 3D-Szene wählen">🎯</button>
                </div>
            </div>

            <hr style="border: 0; border-top: 1px solid #eee; margin: 15px 0;">

            <!-- HARDWARE ZUWEISUNG -->
            <div class="sidebar-form-group">
                <label>Hardware-Komponenten</label>
                <div id="hardware-tags-container" class="tags-container"></div>
                <div style="display:flex; gap:5px; margin-top:5px;">
                    <input list="hardware-datalist" id="hardware-input" placeholder="Hardware suchen..." style="flex-grow:1; padding:8px; border:1px solid #ccc; border-radius:6px; font-size:14px;">
                    <datalist id="hardware-datalist"></datalist>
                    <!-- Kleiner Plus-Button, da 'Change' bei Datalists manchmal tricky ist -->
                    <button id="btn-add-hardware" style="padding:0 10px; cursor:pointer;">+</button>
                </div>
            </div>
            <!-- NEU: LAGERORT VERKNÜPFUNG -->
            <div class="sidebar-form-group">
                <label>Verknüpftes Lager / Rüstwagen</label>
                <div style="display:flex; gap:5px;">
                    <select id="storage-link-dropdown" style="flex-grow:1">
                        <option value="">-- Kein Lager verknüpft --</option>
                    </select>
                </div>
                <p style="font-size:10px; color:#888; margin-top:2px;">Zeigt den Inhalt dieses Lagers in der 3D-Ansicht an.</p>
            </div>

            <hr style="border: 0; border-top: 1px solid #eee; margin: 15px 0;">

            <div class="sidebar-form-group">
                <label>Technische Details</label>
                <div id="details-editor"></div>
                <button id="add-detail-btn" style="margin-top:5px; font-size:12px;">+ Detail hinzufügen</button>
            </div>
            <div class="sidebar-footer">
                <button id="btn-cancel-struct" class="btn-secondary">Zurück</button>
                <button id="btn-save-struct" class="btn-primary">Speichern</button>
            </div>
        </div>

        <!-- VIEW 2: PROZESS LISTE -->
        <div id="view-process-list" class="sidebar-view hidden">
            <div class="flow-filter-container">
                <input type="text" id="flow-filter-text" placeholder="Name suchen...">
                <select id="flow-filter-unit"><option value="">Alle Einheiten</option></select>
                <select id="flow-filter-medium"><option value="">Alle Medien</option></select>
            </div>
            <div class="flow-editor-list-container">
                <ul id="flow-editor-list" class="flow-editor-list"></ul>
            </div>
            <button class="add-flow-btn" id="btn-create-new-flow">+ Neuen Prozessfluss erstellen</button>
        </div>

        <!-- VIEW 3: PROZESS EDITOR -->
        <div id="view-process-edit" class="sidebar-view hidden">
            <div class="sidebar-form-group">
                <label for="flow-detail-name">Name des Flusses</label>
                <input type="text" id="flow-detail-name">
            </div>
            <div class="sidebar-form-group">
                <label for="flow-detail-unit">Zugeordnete Betriebseinheit</label>
                <select id="flow-detail-unit"></select>
            </div>
            <div class="sidebar-form-group">
                <label for="flow-detail-medium">Medium</label>
                <input type="text" id="flow-detail-medium" placeholder="z.B. Wasser">
            </div>
            <div class="sidebar-form-group">
                <label for="flow-detail-color">Farbe</label>
                <input type="color" id="flow-detail-color" value="#ff851b">
            </div>
            <div class="sidebar-form-group">
                <label>Pfadsegmente</label>
                <div id="flow-path-builder">
                    <select id="flow-from-select"></select>
                    <span>→</span>
                    <select id="flow-to-select"></select>
                    <button id="add-flow-segment-btn">+</button>
                </div>
                <ul id="flow-path-segments"></ul>
            </div>
            <div class="sidebar-footer">
                <button id="btn-cancel-edit-flow" class="btn-secondary">Zurück</button>
                <button id="btn-save-flow" class="btn-primary">Speichern</button>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
    <script type="importmap">{ "imports": { "three": "https://unpkg.com/three@0.158.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/" } }</script>
    <script>const initialData = {{ structure_data|tojson|safe }}; const initialWikiData = {{ wiki_data|tojson|safe }}; const IS_ADMIN_OR_PLANNER = {{ 'true' if session.get('is_admin') or 'PLANNER' in session.get('roles', []) else 'false' }};</script>
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';

        let scene, camera, renderer, labelRenderer, controls, raycaster, mouse;
        let allLabels = [], allMaterials = [], meshMap = new Map();
        let isStatusView = true;
        let highlightedObjects = [];
        let targetCameraPosition = null, targetControlsTarget = null;
        let flowLines = [];
        let activeFlowTextures = []; 
        let isPickingMode = false;

        const NEUTRAL_COLOR = new THREE.Color(0xcccccc);
        const NEUTRAL_OPACITY = 0.5;
        const SEARCH_HIGHLIGHT_COLOR = new THREE.Color(0x00ffff);
        const highlightMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00, emissive: 0xccaa00, transparent: true, opacity: 0.4, depthWrite: false, side: 2 });
        const searchMaterial = new THREE.MeshStandardMaterial({ color: SEARCH_HIGHLIGHT_COLOR, emissive: 0x004444, transparent: false, opacity: 0.9 });

        function createFresnelMaterial(color) {
            const uColor = new THREE.Color(color);
            const mat = new THREE.ShaderMaterial({
                uniforms: { uColor: { value: uColor } },
                vertexShader: [
                    'varying vec3 vN;',
                    'varying vec3 vV;',
                    'varying vec3 vLD;',
                    'void main() {',
                    '  vec4 mvPos = modelViewMatrix * vec4(position, 1.0);',
                    '  vN  = normalize(normalMatrix * normal);',
                    '  vV  = -mvPos.xyz;',
                    '  vLD = normalize(mat3(viewMatrix) * vec3(0.51, 0.77, 0.38));',
                    '  gl_Position = projectionMatrix * mvPos;',
                    '}'
                ].join(' '),
                fragmentShader: [
                    'uniform vec3 uColor;',
                    'varying vec3 vN;',
                    'varying vec3 vV;',
                    'varying vec3 vLD;',
                    'void main() {',
                    '  vec3 n = normalize(vN);',
                    '  float fresnel = 1.0 - abs(dot(n, normalize(vV)));',
                    '  float a     = mix(0.04, 0.88, pow(fresnel, 2.5));',
                    '  float diff  = 0.5 + 0.5 * abs(dot(n, normalize(vLD)));',
                    '  gl_FragColor = vec4(uColor * diff, a);',
                    '}'
                ].join(' '),
                transparent: true,
                depthWrite: false,
                side: THREE.DoubleSide,
            });
            // Proxy .color → uniform so toggleViewMode's material.color.copy() works unchanged
            Object.defineProperty(mat, 'color', {
                get: () => mat.uniforms.uColor.value,
                set: (v) => mat.uniforms.uColor.value.copy(v)
            });
            return mat;
        }

        const STATUS_COLORS = { 'frei': new THREE.Color(0x2ecc40), 'heiss': new THREE.Color(0xff4136), 'in_Betrieb': new THREE.Color(0x1565c0), 'Wartung_geplant': new THREE.Color(0x90caf9), 'Standby': new THREE.Color(0xdddddd), 'aktiv': new THREE.Color(0x39cccc), 'N/A': new THREE.Color(0xeeeeee) };

        const HARDWARE_TYPES = [
            "Sensor: Temperatur (PT100)", "Sensor: Durchfluss", "Sensor: Druck",
            "Aktor: Ventil", "Aktor: Pumpe",
            "Steuerung: SPS Modul", "Steuerung: IoT Gateway",
            "Kamera: Überwachung", "Sicherheit: Not-Aus", "Netzwerk: Switch"
        ];

        const allStructureData = initialData;
        let wikiData = initialWikiData;
        let storageSearchIndex = null; // lazy-loaded: { storageName: [{pn,sn,desc},...] }

        function nodeHasWiki(nodeId) {
            const d = wikiData[nodeId];
            if (!d) return false;
            return (d.notizen && d.notizen.trim() !== '') ||
                   (d.links && d.links.length > 0) ||
                   (d.bereich && d.bereich !== '') ||
                   (d.verantwortlicher && d.verantwortlicher.trim() !== '') ||
                   false;
        }

        // --- APP STATE VARIABLES ---
        let currentView = 'betriebseinheiten';
        let appMode = 'structure';
        let isStructureEditMode = false;
        let isDragSortMode = false;

        function generateUUID() {
            if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
                return crypto.randomUUID();
            }
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
                const r = Math.random() * 16 | 0;
                return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
            });
        }
        let currentlyEditingFlowId = null;

        const viewMaps = {
            betriebseinheiten: { meshToPathMap: new Map(), meshDetailsMap: new Map() },
            gebaeude: { meshToPathMap: new Map(), meshDetailsMap: new Map() }
        };
        (allStructureData.custom_layers || []).forEach(layer => {
            viewMaps[layer.id] = { meshToPathMap: new Map(), meshDetailsMap: new Map() };
            if (!allStructureData[layer.id]) allStructureData[layer.id] = [];
        });

        let currentlyExpandedLabel = null;
        let currentlyEditingPath = null;
        let availableMeshNames = [];
        let flowTexture;
        let activeSearchTerm = "";
        let draggedSourcePath = null;

        function getCurrentStructureData() { return allStructureData[currentView]; }
        function getCurrentViewMaps() { return viewMaps[currentView]; }
        const debounce = (func, delay) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), delay); }; };

        function findNodeById(nodes, id) { for (const node of nodes) { if (node.id === id) return node; if (node.children) { const found = findNodeById(node.children, id); if (found) return found; } } return null; }

        function createFlowTexture() { 
            const size = 64;
            const canvas = document.createElement('canvas'); 
            canvas.width = size; canvas.height = size; 
            const ctx = canvas.getContext('2d'); 
            ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'; ctx.fillRect(0, 0, size, size);
            ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
            ctx.beginPath();
            ctx.moveTo(0, 0); ctx.lineTo(32, 32); ctx.lineTo(0, 64);
            ctx.lineTo(16, 64); ctx.lineTo(48, 32); ctx.lineTo(16, 0);
            ctx.fill();
            const texture = new THREE.CanvasTexture(canvas); 
            texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; 
            return texture; 
        }
        async function loadStorageOptions() {
            try {
                const response = await fetch('/api/facility/storage_options');
                const options = await response.json();
                
                const select = document.getElementById('storage-link-dropdown');
                // Ersten Eintrag (Empty) behalten
                select.innerHTML = '<option value="">-- Kein Lager verknüpft --</option>';
                
                options.forEach(opt => {
                    const el = document.createElement('option');
                    el.value = opt.name;
                    el.textContent = `${opt.type}: ${opt.name}`;
                    select.appendChild(el);
                });
            } catch (e) {
                console.error("Fehler beim Laden der Storage Options:", e);
            }
        }
        async function saveStructure() { const indicator = document.getElementById('save-indicator'); indicator.style.opacity = '1'; indicator.textContent = 'Speichern...'; try { const response = await fetch('/api/facility/save_structure', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(allStructureData) }); if (!response.ok) throw new Error('Speichern fehlgeschlagen'); indicator.textContent = 'Gespeichert'; setTimeout(() => { indicator.style.opacity = '0'; }, 2000); } catch (error) { console.error('Fehler:', error); indicator.textContent = 'Fehler!'; indicator.style.color = 'red'; } }
        const debouncedSave = debounce(saveStructure, 1500);
        async function loadHardwareAssets() {
            try {
                const response = await fetch('/api/test_equipment/data');
                const assets = await response.json();
                
                const datalist = document.getElementById('hardware-datalist');
                datalist.innerHTML = '';
                
                assets.forEach(asset => {
                    const opt = document.createElement('option');
                    // Zeige den lesbaren Namen im Dropdown an
                    opt.value = asset.label; 
                    // Speichere die ID in einem data-Attribut für späteren Zugriff
                    opt.dataset.id = asset.id; 
                    datalist.appendChild(opt);
                });
            } catch (e) {
                console.error("Fehler beim Laden der Hardware:", e);
            }
        }
        function init() {
            scene = new THREE.Scene(); scene.background = new THREE.Color(0xf4f6f8);
            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(50, 40, 50); camera.lookAt(0,0,0);
            const canvasContainer = document.getElementById('webgl-canvas-container');
            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio);
            canvasContainer.appendChild(renderer.domElement);

            labelRenderer = new CSS2DRenderer();
            labelRenderer.setSize(window.innerWidth, window.innerHeight);
            document.getElementById('css-label-container').appendChild(labelRenderer.domElement);

            scene.add(new THREE.AmbientLight(0xffffff, 0.8));
            const dirLight = new THREE.DirectionalLight(0xffffff, 0.7);
            dirLight.position.set(20, 30, 15); scene.add(dirLight);
            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;

            raycaster = new THREE.Raycaster();
            mouse = new THREE.Vector2();

            buildMeshDetailsMap(allStructureData.betriebseinheiten, 'betriebseinheiten');
            buildMeshDetailsMap(allStructureData.gebaeude, 'gebaeude');
            (allStructureData.custom_layers || []).forEach(layer => {
                buildMeshDetailsMap(allStructureData[layer.id] || [], layer.id);
            });
            flowTexture = createFlowTexture();

            // Populate Dropdowns
            loadHardwareAssets();
            loadStorageOptions();

            loadModel(); animate(); redrawTree(); setupTreeEventListeners();
            setupSearchListeners();

            // --- EVENT LISTENERS ---
            document.getElementById('toggle-treeview-btn').addEventListener('click', () => { const container = document.getElementById('treeview-container'); container.classList.toggle('visible'); if (!container.classList.contains('visible')) closeWikiPanel(); });

            // Layers-Button: Panel öffnen/schließen
            document.getElementById('layers-btn').addEventListener('click', (e) => { e.stopPropagation(); document.getElementById('layers-popup').classList.toggle('open'); });
            document.addEventListener('click', () => { document.getElementById('layers-popup').classList.remove('open'); });
            document.getElementById('layers-popup').addEventListener('click', (e) => e.stopPropagation());

            // Layers-Panel: Ansicht-Optionen
            document.getElementById('layer-opt-betriebseinheiten').addEventListener('click', () => {
                if (appMode === 'processes') toggleAppMode();
                if (currentView !== 'betriebseinheiten') switchToView('betriebseinheiten');
                document.getElementById('layers-popup').classList.remove('open');
            });
            document.getElementById('layer-opt-gebaeude').addEventListener('click', () => {
                if (appMode === 'processes') toggleAppMode();
                if (currentView !== 'gebaeude') switchToView('gebaeude');
                document.getElementById('layers-popup').classList.remove('open');
            });
            document.getElementById('layer-opt-prozesse').addEventListener('click', () => {
                if (appMode !== 'processes') toggleAppMode();
                document.getElementById('layers-popup').classList.remove('open');
            });

            // Layers-Panel: Darstellung-Toggle (bleibt offen)
            document.getElementById('layer-opt-status').addEventListener('click', () => { toggleViewMode(); });
            document.getElementById('add-root-btn').addEventListener('click', addTopLevelNode);
            document.getElementById('expand-all-btn').addEventListener('click', expandAll);
            document.getElementById('collapse-all-btn').addEventListener('click', collapseAll);
            document.getElementById('toggle-search-btn').addEventListener('click', () => { document.getElementById('search-container').classList.toggle('visible'); });
            document.getElementById('header-back-btn').addEventListener('click', goBackFromEdit);

            // Edit Mode Button nur wenn vorhanden (Admins)
            const toggleStructEditBtn = document.getElementById('toggle-structure-edit-mode-btn');
            if(toggleStructEditBtn) toggleStructEditBtn.addEventListener('click', toggleStructureEditMode);
            const toggleDragSortBtn = document.getElementById('toggle-drag-sort-btn');
            if(toggleDragSortBtn) toggleDragSortBtn.addEventListener('click', toggleDragSortMode);

            // Anlagenbericht (Layers-Panel)
            const auditBtn = document.getElementById('layer-opt-audit');
            if (auditBtn) auditBtn.addEventListener('click', () => { window.open('/facility/audit_report', '_blank'); document.getElementById('layers-popup').classList.remove('open'); });

            // Custom layers: initial render + "Neuer Layer" button
            renderCustomLayerOptions();
            const addNewLayerBtn = document.getElementById('layer-opt-add-new');
            if (addNewLayerBtn) addNewLayerBtn.addEventListener('click', (e) => { e.stopPropagation(); openCreateLayerDialog(); document.getElementById('layers-popup').classList.remove('open'); });
            document.getElementById('new-layer-cancel').addEventListener('click', closeCreateLayerDialog);
            document.getElementById('new-layer-overlay').addEventListener('click', closeCreateLayerDialog);

            // Process Logic
            const toggleFlowEditBtn = document.getElementById('toggle-flow-edit-mode-btn');
            if(toggleFlowEditBtn) toggleFlowEditBtn.addEventListener('click', toggleFlowListEditMode);

            document.getElementById('btn-create-new-flow').addEventListener('click', () => switchSidebarView('process-edit', null));
            document.getElementById('btn-cancel-edit-flow').addEventListener('click', () => switchSidebarView('process-list'));
            document.getElementById('btn-save-flow').addEventListener('click', saveFlowDetails);

            // Structure Edit Logic
            document.getElementById('btn-save-struct').addEventListener('click', saveStructureChanges);
            document.getElementById('btn-cancel-struct').addEventListener('click', () => switchSidebarView('structure'));
            document.getElementById('add-detail-btn').addEventListener('click', () => createDetailRow('', ''));
            document.getElementById('details-editor').addEventListener('click', (e) => { if (e.target.classList.contains('btn-delete-detail')) e.target.parentElement.remove(); });
            document.getElementById('mesh-dropdown').addEventListener('change', handleAddMeshTag);
            document.getElementById('mesh-tags-container').addEventListener('click', (e) => { if(e.target.classList.contains('remove-tag')) handleRemoveMeshTag(e.target.parentElement); });

            // Hardware Logic
            const hwInput = document.getElementById('hardware-input');
            const hwAddBtn = document.getElementById('btn-add-hardware');
            
            // Hinzufügen per Button
            hwAddBtn.addEventListener('click', () => {
                const val = hwInput.value.trim();
                if(val) {
                    handleAddHardwareTag({ target: { value: val } }); // Simuliert das Event-Objekt für die bestehende Funktion
                    hwInput.value = '';
                }
            });
            
            // Hinzufügen per Enter-Taste
            hwInput.addEventListener('keydown', (e) => {
                if(e.key === 'Enter') {
                    e.preventDefault();
                    hwAddBtn.click();
                }
            });

            // Remove-Listener bleibt gleich
            document.getElementById('hardware-tags-container').addEventListener('click', (e) => { if(e.target.classList.contains('remove-tag')) handleRemoveHardwareTag(e.target.parentElement); });
            
            // Picker Logic
            document.getElementById('btn-pick-mesh').addEventListener('click', togglePickingMode);
            renderer.domElement.addEventListener('click', on3DClick);

            // Process Filters
            document.getElementById('flow-filter-text').addEventListener('input', () => populateFlowEditorList());
            document.getElementById('flow-filter-unit').addEventListener('change', () => populateFlowEditorList());
            document.getElementById('flow-filter-medium').addEventListener('change', () => populateFlowEditorList());

            // Path Builder
            document.getElementById('add-flow-segment-btn').addEventListener('click', addSegmentFromSelects);
            document.getElementById('flow-path-segments').addEventListener('click', (e) => { if(e.target.tagName === 'BUTTON') { e.target.parentElement.remove(); updateSegmentNumbers(); } });

            // Wiki Panel
            document.getElementById('wiki-panel-close').addEventListener('click', closeWikiPanel);
            document.getElementById('wiki-add-link-btn').addEventListener('click', () => addWikiLinkRow());
            document.getElementById('wiki-save-btn').addEventListener('click', saveWikiData);
            document.getElementById('wiki-edit-btn').addEventListener('click', () => setWikiMode('edit'));
            document.getElementById('wiki-status').addEventListener('change', updateStatusStyle);
            // History
            document.getElementById('wiki-history-btn').addEventListener('click', openHistoryPanel);
            document.getElementById('wiki-expand-btn').addEventListener('click', cycleWikiPanelSize);
            document.getElementById('wiki-orders-header').addEventListener('click', () => toggleInfoSection(document.getElementById('wiki-orders-section')));
            document.getElementById('wiki-storage-header').addEventListener('click', () => toggleInfoSection(document.getElementById('wiki-storage-section')));
            document.getElementById('wiki-history-close').addEventListener('click', closeHistoryPanel);
            // Suche
            document.getElementById('wiki-search-btn').addEventListener('click', (e) => { e.stopPropagation(); openWikiSearch(); });
            document.getElementById('wiki-search-input').addEventListener('input', (e) => performWikiSearch(e.target.value));
            document.getElementById('wiki-search-popup').addEventListener('click', (e) => e.stopPropagation());
            document.addEventListener('click', (e) => {
                closeWikiSearch();
                if (atPickerActive && !e.target.closest('#wiki-at-picker')) hideAtPicker();
                if (slashActive && !e.target.closest('#wiki-slash-palette')) hideSlashPalette();
            });
            // @ mention + / command triggers
            const wikiTA = document.getElementById('wiki-notizen');
            wikiTA.addEventListener('input', () => { checkAtTrigger(wikiTA); if (!atPickerActive) checkSlashTrigger(wikiTA); });
            wikiTA.addEventListener('keydown', (e) => {
                if (atPickerActive) {
                    if (e.key === 'ArrowDown') { e.preventDefault(); setAtPickerIndex(Math.min(atPickerIndex + 1, atPickerNodes.length - 1)); }
                    else if (e.key === 'ArrowUp') { e.preventDefault(); setAtPickerIndex(Math.max(atPickerIndex - 1, 0)); }
                    else if (e.key === 'Enter' || e.key === 'Tab') { if (atPickerIndex >= 0 && atPickerNodes[atPickerIndex]) { e.preventDefault(); selectAtNode(atPickerNodes[atPickerIndex], wikiTA); } }
                    else if (e.key === 'Escape') { e.preventDefault(); hideAtPicker(); }
                    return;
                }
                if (slashActive) {
                    if (e.key === 'ArrowDown') { e.preventDefault(); setSlashIndex(Math.min(slashIndex + 1, slashItems.length - 1)); }
                    else if (e.key === 'ArrowUp') { e.preventDefault(); setSlashIndex(Math.max(slashIndex - 1, 0)); }
                    else if (e.key === 'Enter' || e.key === 'Tab') { if (slashIndex >= 0 && slashItems[slashIndex]) { e.preventDefault(); executeSlashCommand(slashItems[slashIndex], wikiTA); } }
                    else if (e.key === 'Escape') { e.preventDefault(); hideSlashPalette(); }
                }
            });
            // Drag & drop files onto textarea
            wikiTA.addEventListener('dragover', (e) => { e.preventDefault(); wikiTA.classList.add('drag-over'); });
            wikiTA.addEventListener('dragleave', () => wikiTA.classList.remove('drag-over'));
            wikiTA.addEventListener('drop', (e) => {
                e.preventDefault();
                wikiTA.classList.remove('drag-over');
                const files = [...(e.dataTransfer.files || [])];
                if (!files.length || !currentWikiNodeId) return;
                const indicator = document.getElementById('wiki-upload-indicator');
                const label = document.getElementById('wiki-upload-label');
                indicator.classList.add('visible');
                (async () => {
                    for (const file of files) {
                        label.textContent = 'Lädt hoch: ' + file.name;
                        const fd = new FormData();
                        fd.append('file', file);
                        try {
                            const res = await fetch('/api/facility/wiki/' + currentWikiNodeId + '/attachments', { method: 'POST', body: fd });
                            const data = await res.json();
                            if (data.success) {
                                currentAttachments.push({ name: data.name, original_name: data.original_name, size: data.size, mime: data.mime, uploaded_by: data.uploaded_by, uploaded_at: data.uploaded_at });
                                renderAttachmentList(currentAttachments);
                                const isImg = data.mime && data.mime.startsWith('image/');
                                const snippet = isImg ? '![' + data.original_name + '](attach:' + data.name + ')' : '[' + data.original_name + '](attach:' + data.name + ')';
                                const curPos = wikiTA.selectionStart;
                                wikiTA.value = wikiTA.value.substring(0, curPos) + snippet + wikiTA.value.substring(curPos);
                                wikiTA.selectionStart = wikiTA.selectionEnd = curPos + snippet.length;
                            }
                        } catch(err) { console.error('Drop-Upload fehlgeschlagen:', err); }
                    }
                    indicator.classList.remove('visible');
                })();
            });
            // Attachment upload button
            document.getElementById('wiki-attach-upload-btn').addEventListener('click', () => triggerFileUpload('*', null, false));
            // Lightbox close
            document.getElementById('wiki-lightbox').addEventListener('click', closeLightbox);
            document.getElementById('wiki-lightbox-close').addEventListener('click', (e) => { e.stopPropagation(); closeLightbox(); });
            document.getElementById('wiki-template-select').addEventListener('change', (e) => {
                const tpl = WIKI_TEMPLATES[e.target.value];
                if (!tpl) return;
                const ta = document.getElementById('wiki-notizen');
                if (ta.value.trim() === '' || confirm('Aktuellen Inhalt mit Template ersetzen?')) {
                    ta.value = tpl; ta.focus();
                }
                e.target.value = '';
            });

            window.addEventListener('resize', onWindowResize);
        }

        function animate() { 
            requestAnimationFrame(animate); 
            activeFlowTextures.forEach(tex => { tex.offset.x -= 0.01; });
            if (targetCameraPosition && targetControlsTarget) { camera.position.lerp(targetCameraPosition, 0.08); controls.target.lerp(targetControlsTarget, 0.08); if (camera.position.distanceTo(targetCameraPosition) < 0.1) { camera.position.copy(targetCameraPosition); controls.target.copy(targetControlsTarget); targetCameraPosition = null; targetControlsTarget = null; } } controls.update(); renderer.render(scene, camera); labelRenderer.render(scene, camera); 
        }
        function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); labelRenderer.setSize(window.innerWidth, window.innerHeight); }

        function togglePickingMode() {
            isPickingMode = !isPickingMode;
            const btn = document.getElementById('btn-pick-mesh');
            if (isPickingMode) { btn.classList.add('active'); document.body.classList.add('picking-mode'); } else { btn.classList.remove('active'); document.body.classList.remove('picking-mode'); }
        }

        function on3DClick(event) {
            if (!isPickingMode) return;
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
            raycaster.setFromCamera(mouse, camera);
            const intersects = raycaster.intersectObjects(scene.children, true);
            if (intersects.length > 0) {
                const hit = intersects.find(i => i.object.isMesh);
                if (hit) { handleAddMeshTag({target: {value: hit.object.name}}); togglePickingMode(); }
            }
        }

        function toggleAppMode() {
            if (appMode === 'structure') {
                appMode = 'processes';
                switchSidebarView('process-list');
                document.getElementById('treeview-container').classList.add('visible');
                document.getElementById('view-process-list').classList.remove('editing');
                const flowBtn = document.getElementById('toggle-flow-edit-mode-btn');
                if(flowBtn) flowBtn.classList.remove('active');
                populateFlowFilterOptions();
                populateFlowEditorList();
                if (allStructureData.fluesse && allStructureData.fluesse.length > 0) {
                    showFlow(allStructureData.fluesse[0].id);
                }
            } else {
                appMode = 'structure';
                switchSidebarView('structure');
                if(isStructureEditMode) toggleStructureEditMode();
                clearFlowView();
            }
            updateLayersPanelState();
        }

        function toggleStructureEditMode() {
            isStructureEditMode = !isStructureEditMode;
            const btn = document.getElementById('toggle-structure-edit-mode-btn');
            const dragSortBtn = document.getElementById('toggle-drag-sort-btn');
            const treeContainer = document.getElementById('tree');
            const addRootBtn = document.getElementById('add-root-btn');
            const root = document.documentElement;

            if (isStructureEditMode) {
                btn.classList.add('active');
                treeContainer.classList.add('editing');
                addRootBtn.style.display = 'inline-block';
                dragSortBtn.style.display = 'inline-flex';
                root.style.setProperty('--panel-width', '600px');
            }
            else {
                btn.classList.remove('active');
                treeContainer.classList.remove('editing');
                addRootBtn.style.display = 'none';
                dragSortBtn.style.display = 'none';
                if (isDragSortMode) toggleDragSortMode();
                root.style.setProperty('--panel-width', '500px');
            }
            redrawTree();
        }

        function toggleDragSortMode() {
            isDragSortMode = !isDragSortMode;
            const btn = document.getElementById('toggle-drag-sort-btn');
            const treeContainer = document.getElementById('tree');
            if (isDragSortMode) {
                btn.classList.add('active');
                treeContainer.classList.add('drag-sorting');
            } else {
                btn.classList.remove('active');
                treeContainer.classList.remove('drag-sorting');
            }
            redrawTree();
        }

        function goBackFromEdit() { if (appMode === 'processes') switchSidebarView('process-list'); else switchSidebarView('structure'); }

function switchSidebarView(viewName, dataId = null) {
            const viewStructure = document.getElementById('view-structure');
            const viewStructureEdit = document.getElementById('view-structure-edit');
            const viewProcessList = document.getElementById('view-process-list');
            const viewProcessEdit = document.getElementById('view-process-edit');
            const header = document.getElementById('panel-header');
            const structActions = document.getElementById('structure-actions');
            const procActions = document.getElementById('process-actions');
            const backAction = document.getElementById('header-back-action');

            viewStructure.classList.add('hidden');
            viewStructureEdit.classList.add('hidden');
            viewProcessList.classList.add('hidden');
            viewProcessEdit.classList.add('hidden');

            header.classList.remove('with-border');
            structActions.style.display = 'none';
            procActions.style.display = 'none';
            backAction.style.display = 'none';

            if (viewName === 'structure') {
                viewStructure.classList.remove('hidden'); structActions.style.display = 'flex';
                document.getElementById('sidebar-title').textContent = getViewTitle(currentView);

            } else if (viewName === 'structure-edit') {
                viewStructureEdit.classList.remove('hidden'); header.classList.add('with-border');
                backAction.style.display = 'block';
                populateStructureEditor(dataId);

            } else if (viewName === 'process-list') {
                viewProcessList.classList.remove('hidden'); header.classList.add('with-border');
                procActions.style.display = 'flex';
                document.getElementById('sidebar-title').textContent = 'Prozessflüsse';
                populateFlowEditorList();

            } else if (viewName === 'process-edit') {
                viewProcessEdit.classList.remove('hidden'); header.classList.add('with-border');
                backAction.style.display = 'block';
                currentlyEditingFlowId = dataId;
                document.getElementById('sidebar-title').textContent = dataId ? 'Prozess bearbeiten' : 'Neuer Prozess';
                populateFlowDetailForm(dataId);
            }
        }

        function openStructureEditor(path) { currentlyEditingPath = path; switchSidebarView('structure-edit', path); }

        function populateStructureEditor(path) {
            const node = getNodeByPath(path);
            if (!node) return;

            const pathParts = path.split('.');
            let displayIndex = "";
            pathParts.forEach(part => { if (!isNaN(part)) { displayIndex += (parseInt(part) + 1) + "."; } });
            if (displayIndex.length > 0) displayIndex = displayIndex.slice(0, -1);

            document.getElementById('sidebar-title').textContent = `${displayIndex} Eintrag bearbeiten`;
            document.getElementById('struct-edit-name').value = node.name || '';
            document.getElementById('struct-edit-label-name').value = node.labelName || '';
            document.getElementById('struct-edit-show-label').checked = node.showStatusLabel !== false;
            document.getElementById('storage-link-dropdown').value = node.storageLink || '';
            const detailsEditor = document.getElementById('details-editor'); detailsEditor.innerHTML = '';
            if (node.details) { for (const key in node.details) { createDetailRow(key, node.details[key]); } }

            populateMeshEditor(node.meshNames || []);
            populateHardwareEditor(node.hardware || []);
        }

        function saveStructureChanges() {
            if (!currentlyEditingPath) return;
            const node = getNodeByPath(currentlyEditingPath);
            if (!node) return;

            node.name = document.getElementById('struct-edit-name').value;
            const labelName = document.getElementById('struct-edit-label-name').value.trim();
            if (labelName) node.labelName = labelName; else delete node.labelName;
            node.showStatusLabel = document.getElementById('struct-edit-show-label').checked;

            const newDetails = {};
            document.querySelectorAll('#details-editor .detail-row').forEach(row => {
                const key = row.querySelector('.detail-key').value.trim();
                const value = row.querySelector('.detail-value').value.trim();
                if (key) newDetails[key] = value;
            });
            node.details = newDetails;
            node.meshNames = getCurrentModalMeshNames();
            node.hardware = getCurrentHardwareNames();
            node.storageLink = document.getElementById('storage-link-dropdown').value;
            
            buildMeshDetailsMap(getCurrentStructureData(), currentView);
            redrawTree();
            updateAllLabels();
            debouncedSave();
            switchSidebarView('structure');
        }

        function setupSearchListeners() {
            const searchInput = document.getElementById('search-input');
            searchInput.addEventListener('input', (e) => { activeSearchTerm = e.target.value.toLowerCase(); filterTreeAndScene(activeSearchTerm); });
            document.querySelectorAll('.filter-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active');
                    const filter = e.target.dataset.filter;
                    if(filter === 'all') { searchInput.value = ""; activeSearchTerm = ""; }
                    else if(filter === 'critical') { searchInput.value = "heiss"; activeSearchTerm = "heiss"; }
                    else if(filter === 'active') { searchInput.value = "betrieb"; activeSearchTerm = "betrieb"; }
                    filterTreeAndScene(activeSearchTerm);
                });
            });
        }

        function filterTreeAndScene(term) {
            const treeItems = document.querySelectorAll('#tree .tree-item');
            const matchingMeshNames = new Set();
            treeItems.forEach(item => {
                const label = item.querySelector('.tree-label').textContent.toLowerCase();
                const path = item.dataset.path;
                const node = getNodeByPath(path);

                let detailsMatch = false;
                if(node.details) { for(let k in node.details) if(String(node.details[k]).toLowerCase().includes(term)) detailsMatch = true; }

                let hardwareMatch = false;
                if(node.hardware) { node.hardware.forEach(h => { if(h.toLowerCase().includes(term)) hardwareMatch = true; }); }

                if (term === "" || label.includes(term) || detailsMatch || hardwareMatch) {
                    item.style.display = "";
                    let parent = item.parentElement.closest('.tree-item');
                    while(parent) { parent.style.display = ""; parent.classList.add('open'); parent = parent.parentElement.closest('.tree-item'); }
                    if(term !== "") item.querySelector('.tree-row').classList.add('search-match'); else item.querySelector('.tree-row').classList.remove('search-match');
                    if(node.meshNames) node.meshNames.forEach(m => matchingMeshNames.add(m));
                } else { item.style.display = "none"; item.querySelector('.tree-row').classList.remove('search-match'); }
            });
            if(term === "") { meshMap.forEach(mesh => { if(mesh.userData.originalMaterial) mesh.material = mesh.userData.originalMaterial; mesh.visible = true; }); } 
            else { meshMap.forEach((mesh, name) => { if(matchingMeshNames.has(name)) { mesh.visible = true; mesh.material = searchMaterial; } else { mesh.material = new THREE.MeshBasicMaterial({ color: 0xcccccc, transparent: true, opacity: 0.1, depthWrite: false }); } }); }
        }

        function getViewTitle(viewId) {
            if (viewId === 'betriebseinheiten') return 'Betriebseinheiten';
            if (viewId === 'gebaeude') return 'Gebäude';
            const cl = (allStructureData.custom_layers || []).find(l => l.id === viewId);
            return cl ? cl.name : viewId;
        }
        function switchToView(targetView) {
            if (!viewMaps[targetView] || currentView === targetView) return;
            currentView = targetView;
            document.getElementById('sidebar-title').textContent = getViewTitle(targetView);
            highlightObjects([]); redrawTree(); updateAllLabels();
            document.getElementById('search-input').value = "";
            filterTreeAndScene("");
            updateLayersPanelState();
        }
        function switchTreeView() { switchToView(currentView === 'betriebseinheiten' ? 'gebaeude' : 'betriebseinheiten'); }

        function clearFlowView() {
            flowLines.forEach(line => { scene.remove(line); line.geometry.dispose(); line.material.map?.dispose(); line.material.dispose(); });
            flowLines = [];
            activeFlowTextures.forEach(t => t.dispose()); activeFlowTextures = [];
            meshMap.forEach(mesh => { if (mesh.userData.originalMaterial) { mesh.material = mesh.userData.originalMaterial; } });
            allMaterials.forEach(item => { item.material.opacity = 0.8; item.material.transparent = true; });
            highlightObjects([]);
            if(isStatusView) { allLabels.forEach(label => { label.visible = true; label.element.style.opacity = "1"; label.element.style.pointerEvents = "auto"; }); }
            document.getElementById('active-flow-indicator').style.display = 'none';
            document.querySelectorAll('.flow-editor-list li.active').forEach(el => el.classList.remove('active'));
        }

        function showFlow(flowId) {
            clearFlowView();
            const flow = allStructureData.fluesse.find(f => f.id === flowId);
            if (!flow) return;
            const activeListItem = document.querySelector(`.flow-name[data-flow-id="${flowId}"]`)?.closest('li');
            if (activeListItem) activeListItem.classList.add('active');
            const indicator = document.getElementById('active-flow-indicator');
            let indicatorHtml = `Aktiver Prozess: <span>${flow.name}</span>`;
            if(flow.medium) indicatorHtml += `<span class="medium-tag">${flow.medium}</span>`;
            indicatorHtml += `<button id="reopen-flow-editor-btn" title="Zurück zur Liste" style="margin-left:15px; font-size:16px;">☰</button>`;
            indicatorHtml += `<button id="close-flow-btn" title="Ansicht schließen">×</button>`;
            indicator.innerHTML = indicatorHtml;
            indicator.style.display = 'flex';
            document.getElementById('close-flow-btn').onclick = clearFlowView;
            document.getElementById('reopen-flow-editor-btn').onclick = () => { if(appMode !== 'processes') toggleAppMode(); document.getElementById('treeview-container').classList.add('visible'); };
            let allNodeIdsInFlow = new Set();
            flow.path.forEach(segment => { allNodeIdsInFlow.add(segment.from); allNodeIdsInFlow.add(segment.to); });
            const meshesInFlow = [];
            allNodeIdsInFlow.forEach(nodeId => { const node = findNodeById(allStructureData.betriebseinheiten, nodeId); if (node && node.meshNames) { meshesInFlow.push(...node.meshNames); } });
            const flowSet = new Set(meshesInFlow);
            allLabels.forEach(label => {
                if (flowSet.has(label.userData.meshName)) { label.visible = true; label.element.style.opacity = "1"; label.element.style.pointerEvents = "auto"; } 
                else { label.visible = false; label.element.style.opacity = "0"; label.element.style.pointerEvents = "none"; }
            });
            const combinedBox = new THREE.Box3();
            let hasObjects = false;
            flowSet.forEach(meshName => { const obj = meshMap.get(meshName); if (obj) { combinedBox.expandByObject(obj); hasObjects = true; } });
            if (hasObjects) {
                const center = combinedBox.getCenter(new THREE.Vector3()); const size = combinedBox.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const cameraDistance = maxDim * 1.5;
                targetControlsTarget = center; targetCameraPosition = new THREE.Vector3(center.x + cameraDistance * 0.6, center.y + cameraDistance * 0.8, center.z + cameraDistance * 0.8);
            }
            meshMap.forEach((mesh, name) => { if (flowSet.has(name)) { if (mesh.userData.originalMaterial) mesh.material = mesh.userData.originalMaterial; } else { mesh.material = new THREE.MeshStandardMaterial({ color: 0xcccccc, opacity: 0.05, transparent: true, depthWrite: false }); } });
            const adjacency = {}; flow.path.forEach(seg => { adjacency[seg.from] = seg.to; });
            const visited = new Set(); const chains = [];
            flow.path.forEach(seg => { if(!visited.has(seg.from)) { let currentChain = [seg.from]; let curr = seg.from; visited.add(curr); while(adjacency[curr]) { let next = adjacency[curr]; currentChain.push(next); visited.add(next); curr = next; } if(currentChain.length > 1) chains.push(currentChain); } });
            chains.forEach(chain => {
                const points = []; let validChain = true;
                chain.forEach(nodeId => { const node = findNodeById(allStructureData.betriebseinheiten, nodeId); const meshName = node?.meshNames?.[0]; const obj = meshName ? meshMap.get(meshName) : null; if(obj) { const center = new THREE.Box3().setFromObject(obj).getCenter(new THREE.Vector3()); center.y = 0.5; points.push(center); } else { validChain = false; } });
                if(validChain && points.length > 1) {
                    const curve = new THREE.CatmullRomCurve3(points); curve.curveType = 'catmullrom'; curve.tension = 0.1; const length = curve.getLength();
                    const instanceTexture = flowTexture.clone(); instanceTexture.repeat.set(length / 8, 1); instanceTexture.needsUpdate = true; activeFlowTextures.push(instanceTexture);
                    const tubeMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color(flow.color), map: instanceTexture, transparent: true, opacity: 0.8, side: THREE.DoubleSide });
                    const tubeGeo = new THREE.TubeGeometry(curve, Math.floor(length * 2), 0.6, 8, false); const tube = new THREE.Mesh(tubeGeo, tubeMaterial);
                    flowLines.push(tube); scene.add(tube);
                }
            });
        }

        function toggleFlowListEditMode() {
            const container = document.getElementById('view-process-list'); const btn = document.getElementById('toggle-flow-edit-mode-btn');
            if(container.classList.contains('editing')) { container.classList.remove('editing'); btn.classList.remove('active'); } 
            else { container.classList.add('editing'); btn.classList.add('active'); }
        }

        function populateFlowFilterOptions() {
            const unitSelect = document.getElementById('flow-filter-unit'); const selectedUnit = unitSelect.value;
            unitSelect.innerHTML = '<option value="">Alle Einheiten</option>';
            const allUnits = flattenTreeForSelect(allStructureData.betriebseinheiten);
            allUnits.forEach(u => { const opt = document.createElement('option'); opt.value = u.id; opt.textContent = u.name; unitSelect.appendChild(opt); });
            unitSelect.value = selectedUnit;
            const mediumSelect = document.getElementById('flow-filter-medium'); const selectedMedium = mediumSelect.value;
            mediumSelect.innerHTML = '<option value="">Alle Medien</option>';
            const mediaSet = new Set();
            (allStructureData.fluesse || []).forEach(f => { if(f.medium && f.medium.trim() !== '') mediaSet.add(f.medium.trim()); });
            Array.from(mediaSet).sort().forEach(m => { const opt = document.createElement('option'); opt.value = m; opt.textContent = m; mediumSelect.appendChild(opt); });
            mediumSelect.value = selectedMedium;
        }

        function populateFlowEditorList() {
            const list = document.getElementById('flow-editor-list'); list.innerHTML = '';
            const filterText = document.getElementById('flow-filter-text').value.toLowerCase();
            const filterUnit = document.getElementById('flow-filter-unit').value;
            const filterMedium = document.getElementById('flow-filter-medium').value;
            (allStructureData.fluesse || []).forEach(flow => {
                if (filterText && !flow.name.toLowerCase().includes(filterText)) return;
                if (filterUnit && flow.associatedUnitId !== filterUnit) return;
                if (filterMedium && flow.medium !== filterMedium) return;
                const li = document.createElement('li');
                let badgesHtml = '<div class="flow-badges">';
                if (flow.associatedUnitId) { const unitNode = findNodeById(allStructureData.betriebseinheiten, flow.associatedUnitId); if (unitNode) { badgesHtml += `<span class="flow-unit-badge">Einheit: ${unitNode.name}</span>`; } }
                if (flow.medium) { badgesHtml += `<span class="flow-medium-badge">Medium: ${flow.medium}</span>`; }
                badgesHtml += '</div>';
                li.innerHTML = `<span class="flow-name" data-flow-id="${flow.id}"><strong>${flow.name}</strong>${badgesHtml}</span> <div class="flow-actions"><span class="action-icon edit" data-action="edit" data-flow-id="${flow.id}">&#9998;</span> <span class="action-icon delete" data-action="delete" data-flow-id="${flow.id}">×</span></div>`;
                list.appendChild(li);
            });
            list.onclick = (e) => {
                const target = e.target;
                const flowNameSpan = target.closest('.flow-name');
                if(flowNameSpan) { showFlow(flowNameSpan.dataset.flowId); return; }
                const actionTarget = target.closest('.action-icon');
                if(actionTarget?.dataset.action === 'edit') { switchSidebarView('process-edit', actionTarget.dataset.flowId); }
                if(actionTarget?.dataset.action === 'delete') { deleteFlow(actionTarget.dataset.flowId); }
            };
        }

        function deleteFlow(flowId) { if (!confirm("Sicher?")) return; allStructureData.fluesse = allStructureData.fluesse.filter(f => f.id !== flowId); debouncedSave(); populateFlowFilterOptions(); populateFlowEditorList(); }

        function flattenTreeForSelect(nodes, namePrefix = '', numberPrefix = '', result = []) {
            nodes.forEach((node, index) => {
                const currentNumber = numberPrefix + (index + 1); result.push({ id: node.id, name: `${currentNumber} ${namePrefix}${node.name}` });
                if (node.children) { flattenTreeForSelect(node.children, namePrefix + '  ', currentNumber + '.', result); }
            });
            return result;
        }

        function populateFlowDetailForm(flowId) {
            const flow = flowId ? allStructureData.fluesse.find(f => f.id === flowId) : null;
            document.getElementById('flow-detail-name').value = flow ? flow.name : '';
            document.getElementById('flow-detail-medium').value = flow ? flow.medium || '' : ''; 
            document.getElementById('flow-detail-color').value = flow ? flow.color : '#ff851b';
            const unitSelect = document.getElementById('flow-detail-unit'); unitSelect.innerHTML = '<option value="">-- Keine Zuordnung --</option>';
            const allUnits = flattenTreeForSelect(allStructureData.betriebseinheiten);
            allUnits.forEach(u => { const opt = document.createElement('option'); opt.value = u.id; opt.textContent = u.name; unitSelect.appendChild(opt); });
            if (flow && flow.associatedUnitId) { unitSelect.value = flow.associatedUnitId; }
            const fromSelect = document.getElementById('flow-from-select'); const toSelect = document.getElementById('flow-to-select');
            fromSelect.innerHTML = toSelect.innerHTML = '<option value="">-- Wählen --</option>';
            allUnits.forEach(opt => { const optionEl = document.createElement('option'); optionEl.value = opt.id; optionEl.textContent = opt.name; fromSelect.appendChild(optionEl.cloneNode(true)); toSelect.appendChild(optionEl); });
            const segmentsList = document.getElementById('flow-path-segments'); segmentsList.innerHTML = '';
            if (flow && flow.path) { flow.path.forEach(segment => addSegmentToList(segment.from, segment.to)); }
            updateSegmentNumbers();
            if (!segmentsList._sortable) { segmentsList._sortable = Sortable.create(segmentsList, { animation: 150, handle: '.seg-handle', ghostClass: 'sortable-ghost', onEnd: updateSegmentNumbers }); }
        }

        function addSegmentFromSelects() { const fromSelect = document.getElementById('flow-from-select'); const toSelect = document.getElementById('flow-to-select'); const fromId = fromSelect.value; const toId = toSelect.value; if (fromId && toId && fromId !== toId) { addSegmentToList(fromId, toId); updateSegmentNumbers(); fromSelect.value = toId; toSelect.value = ''; } }
        function addSegmentToList(fromId, toId) { const fromNode = findNodeById(allStructureData.betriebseinheiten, fromId); const toNode = findNodeById(allStructureData.betriebseinheiten, toId); if (!fromNode || !toNode) return; const segmentsList = document.getElementById('flow-path-segments'); const li = document.createElement('li'); li.dataset.fromId = fromId; li.dataset.toId = toId; li.innerHTML = `<span class="seg-handle">⠿</span><span class="seg-num"></span><span class="seg-label">${fromNode.name} → ${toNode.name}</span><button>×</button>`; segmentsList.appendChild(li); }
        function updateSegmentNumbers() { document.querySelectorAll('#flow-path-segments li').forEach((li, i) => { const num = li.querySelector('.seg-num'); if (num) num.textContent = (i + 1) + '.'; }); }

        function saveFlowDetails() {
            const newName = document.getElementById('flow-detail-name').value.trim();
            const newMedium = document.getElementById('flow-detail-medium').value.trim();
            const associatedUnitId = document.getElementById('flow-detail-unit').value; 
            if (!newName) { alert("Bitte Namen eingeben."); return; }
            const newPath = []; document.querySelectorAll('#flow-path-segments li').forEach(li => { newPath.push({ from: li.dataset.fromId, to: li.dataset.toId }); });
            const newFlow = { id: currentlyEditingFlowId || generateUUID(), name: newName, medium: newMedium, associatedUnitId: associatedUnitId, color: document.getElementById('flow-detail-color').value, path: newPath };
            if (currentlyEditingFlowId) { const index = allStructureData.fluesse.findIndex(f => f.id === currentlyEditingFlowId); allStructureData.fluesse[index] = newFlow; } else { if (!allStructureData.fluesse) allStructureData.fluesse = []; allStructureData.fluesse.push(newFlow); }
            debouncedSave(); switchSidebarView('process-list');
        }

        function getMeshNamesRecursively(node) { let names = []; if (node.meshNames) { names.push(...node.meshNames); } if (node.children) { node.children.forEach(child => { names.push(...getMeshNamesRecursively(child)); }); } return [...new Set(names)]; }
        function highlightObjects(namesArray = []) { highlightedObjects.forEach(obj => { if(obj.userData.originalMaterial) obj.material = obj.userData.originalMaterial; }); highlightedObjects = []; namesArray.forEach(name => { const objectToHighlight = meshMap.get(name); if (objectToHighlight) { if (!objectToHighlight.userData.originalMaterial) { objectToHighlight.userData.originalMaterial = objectToHighlight.material; } objectToHighlight.material = highlightMaterial; highlightedObjects.push(objectToHighlight); } }); }
        function flyToAndHighlight(path) { if (!path) return; clearFlowView(); const node = getNodeByPath(path); if (!node) return; document.getElementById('treeview-container').classList.add('visible'); document.querySelectorAll('.tree-row.active').forEach(el => el.classList.remove('active')); const treeItem = document.querySelector(`.tree-item[data-path="${path}"]`); if (treeItem) { const treeRow = treeItem.querySelector('.tree-row'); treeRow.classList.add('active'); let parent = treeItem.parentElement.closest('.tree-item'); while (parent) { parent.classList.add('open'); parent = parent.parentElement.closest('.tree-item'); } treeRow.scrollIntoView({ behavior: 'smooth', block: 'center' }); } const meshNames = getMeshNamesRecursively(node); highlightObjects(meshNames); if (meshNames.length === 0) return; const combinedBox = new THREE.Box3(); meshNames.forEach(name => { const targetObject = meshMap.get(name); if (targetObject) { combinedBox.expandByObject(targetObject); } }); if (combinedBox.isEmpty()) return; const center = combinedBox.getCenter(new THREE.Vector3()); const size = combinedBox.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const cameraDistance = maxDim * 2.5; targetControlsTarget = center; targetCameraPosition = new THREE.Vector3(center.x + cameraDistance / 2, center.y + cameraDistance / 2, center.z + cameraDistance); }
        function createDetailRow(key, value) { const editor = document.getElementById('details-editor'); const row = document.createElement('div'); row.className = 'detail-row'; row.innerHTML = `<input type="text" class="detail-key" placeholder="Schlüssel" value="${key}"><input type="text" class="detail-value" placeholder="Wert" value="${value}"><button class="btn-delete-detail">×</button>`; editor.appendChild(row); }
        function populateMeshEditor(currentMeshNames = []) { const tagsContainer = document.getElementById('mesh-tags-container'); const dropdown = document.getElementById('mesh-dropdown'); tagsContainer.innerHTML = ''; dropdown.innerHTML = '<option value="">-- Mesh hinzufügen --</option>'; const assigned = new Set(currentMeshNames); currentMeshNames.forEach(name => { const tag = document.createElement('span'); tag.className = 'mesh-tag'; tag.textContent = name; tag.dataset.meshName = name; tag.innerHTML += ' <span class="remove-tag">×</span>'; tagsContainer.appendChild(tag); }); availableMeshNames.filter(name => !assigned.has(name)).forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; dropdown.appendChild(option); }); }
        function handleAddMeshTag(event) { const selectedName = event.target.value; if (!selectedName) return; populateMeshEditor([...getCurrentModalMeshNames(), selectedName]); event.target.value = ""; }
        function handleRemoveMeshTag(tagElement) { const nameToRemove = tagElement.dataset.meshName; populateMeshEditor(getCurrentModalMeshNames().filter(name => name !== nameToRemove)); }
        function getCurrentModalMeshNames() { return Array.from(document.querySelectorAll('#mesh-tags-container .mesh-tag')).map(tag => tag.dataset.meshName); }

        // --- HARDWARE MANAGEMENT LOGIC ---
        function populateHardwareEditor(currentHardware = []) {
            const tagsContainer = document.getElementById('hardware-tags-container'); 
            tagsContainer.innerHTML = '';
            
            currentHardware.forEach(entry => { 
                let name = entry;
                let id = null;
        
                // Versuche JSON zu parsen
                try {
                    if (entry.startsWith('{')) {
                        const obj = JSON.parse(entry);
                        name = obj.label;
                        id = obj.id;
                    }
                } catch(e) {
                    // Fallback: Alter Plain-Text String
                }
        
                const tag = document.createElement('span'); 
                tag.className = 'hardware-tag'; 
                tag.textContent = name; 
                tag.dataset.hwName = name; 
                if (id) tag.dataset.hwId = id; // WICHTIG: ID speichern
        
                tag.innerHTML += ' <span class="remove-tag">×</span>'; 
                tagsContainer.appendChild(tag); 
            });
        }
        function handleAddHardwareTag(event) {
            const selectedLabel = event.target.value;
            if (!selectedLabel) return;
        
            // Finde die Option im Datalist, um an die ID zu kommen
            const datalist = document.getElementById('hardware-datalist');
            const selectedOption = Array.from(datalist.options).find(opt => opt.value === selectedLabel);
            const selectedId = selectedOption ? selectedOption.dataset.id : null;
        
            const currentNames = getCurrentHardwareNames();
            if (currentNames.includes(selectedLabel)) {
                event.target.value = ""; // Eingabe zurücksetzen, wenn schon vorhanden
                return;
            }
        
            const tagsContainer = document.getElementById('hardware-tags-container');
            const tag = document.createElement('span');
            tag.className = 'hardware-tag';
            tag.textContent = selectedLabel;
            tag.dataset.hwName = selectedLabel; // Der lesbare Name
            if (selectedId) {
                tag.dataset.hwId = selectedId; // Die UUID
            }
            tag.innerHTML += ' <span class="remove-tag">×</span>';
            tagsContainer.appendChild(tag);
            
            event.target.value = ""; // Eingabe zurücksetzen
        }        
        function handleRemoveHardwareTag(tagElement) {
            // 1. Identifiziere, was gelöscht werden soll (ID oder Name)
            const idToRemove = tagElement.dataset.hwId;
            const nameToRemove = tagElement.dataset.hwName;
        
            // 2. Hole den aktuellen Zustand (Mischung aus JSON-Strings und Text)
            const currentList = getCurrentHardwareNames();
        
            // 3. Filtere die Liste intelligent
            const newList = currentList.filter(entry => {
                let entryId = null;
                let entryName = entry;
        
                // Versuche, den Eintrag zu parsen, falls es JSON ist
                try {
                    if (entry.startsWith('{')) {
                        const obj = JSON.parse(entry);
                        entryId = obj.id;
                        entryName = obj.label;
                    }
                } catch(e) {}
        
                // Logik:
                // Wenn wir eine ID zum Löschen haben, muss die ID matchen (stärkeres Kriterium).
                if (idToRemove) {
                    return entryId !== idToRemove; 
                }
                
                // Wenn wir keine ID haben (Altdaten/Freitext), muss der Name matchen.
                // Wichtig: Wir löschen nur, wenn auch der Eintrag KEINE ID hat, um Verwechslungen zu vermeiden.
                if (!idToRemove && !entryId) {
                     return entryName !== nameToRemove;
                }
        
                // Sonst behalten
                return true;
            });
        
            // 4. Neu rendern
            populateHardwareEditor(newList);
        }        
        function getCurrentHardwareNames() { 
            return Array.from(document.querySelectorAll('#hardware-tags-container .hardware-tag')).map(tag => {
                if (tag.dataset.hwId) {
                    // Wenn wir eine ID haben, speichern wir das strukturierte JSON
                    return JSON.stringify({ id: tag.dataset.hwId, label: tag.dataset.hwName });
                }
                // Fallback für alte Einträge oder Freitext ohne ID
                return tag.dataset.hwName;
            }); 
        }

        function buildMeshDetailsMap(nodes, viewKey, pathPrefix = '', numberPrefix = '') { 
            if (!pathPrefix) { 
                viewMaps[viewKey].meshToPathMap.clear(); 
                viewMaps[viewKey].meshDetailsMap.clear(); 
            } 
            
            nodes.forEach((node, index) => { 
                if(!node.id) node.id = generateUUID(); 
                
                const currentPath = pathPrefix ? `${pathPrefix}.children.${index}` : `${index}`; 
                const currentNumber = numberPrefix + (index + 1);
                const displayName = node.labelName || node.name;
                const fullDisplayName = `${currentNumber} ${displayName}`;
                node._runtime_fullName = fullDisplayName;
        
                if (node.meshNames) { 
                    node.meshNames.forEach(meshName => { 
                        if (!viewMaps[viewKey].meshToPathMap.has(meshName)) { 
                            viewMaps[viewKey].meshToPathMap.set(meshName, currentPath); 
                        } 
                        viewMaps[viewKey].meshDetailsMap.set(meshName, node); 
                    }); 
                } 
                
                if (node.children) { 
                    buildMeshDetailsMap(node.children, viewKey, currentPath, currentNumber + '.'); 
                } 
            }); 
        }        
        function addTopLevelNode() { getCurrentStructureData().push({ id: generateUUID(), name: "Neuer Überpunkt", children: [], details: {}, hardware: [] }); buildMeshDetailsMap(getCurrentStructureData(), currentView); redrawTree(); debouncedSave(); }
        function expandAll() { document.querySelectorAll('#tree .tree-item').forEach(li => { if(li.querySelector('ul')) li.classList.add('open'); }); }
        function collapseAll() { document.querySelectorAll('#tree .tree-item.open').forEach(li => { li.classList.remove('open'); }); }
        function getParentAndIndexByPath(pathString) { const structure = getCurrentStructureData(); const pathParts = pathString.split('.children.'); const index = parseInt(pathParts.pop()); if (pathParts.length === 0) { return { parent: structure, index }; } const parentPath = pathParts.join('.children.'); const parentNode = getNodeByPath(parentPath); return { parent: parentNode ? parentNode.children : null, index }; }
        function getNodeByPath(pathString) { if (!pathString) return null; const path = pathString.split('.children.'); let current = { children: getCurrentStructureData() }; for(const part of path) { if (current && current.children && typeof current.children[part] !== 'undefined') { current = current.children[part]; } else { return null; } } return current; }
        function addNode(path) { const parentNode = getNodeByPath(path); if (!parentNode) return; if (!parentNode.children) parentNode.children = []; parentNode.children.push({ id: generateUUID(), name: "Neues Bauteil", children: [], details: {}, hardware: [] }); buildMeshDetailsMap(getCurrentStructureData(), currentView); redrawTree(path); debouncedSave(); }
        function deleteNode(path) { if (!confirm("Sicher?")) return; const { parent, index } = getParentAndIndexByPath(path); if (parent && index > -1) { parent.splice(index, 1); buildMeshDetailsMap(getCurrentStructureData(), currentView); redrawTree(); debouncedSave(); } }
        function redrawTree(pathHint = null) { 
            const openPaths = new Set(Array.from(document.querySelectorAll('.tree-item.open')).map(li => li.dataset.path)); 
            if (pathHint) openPaths.add(pathHint); 
            const treeContainer = document.getElementById('tree'); treeContainer.innerHTML = ''; 
            createTreeView(getCurrentStructureData(), treeContainer); 
            openPaths.forEach(path => { const li = treeContainer.querySelector(`li[data-path="${path}"]`); if (li) li.classList.add('open'); }); 
            if(document.getElementById('search-input').value) filterTreeAndScene(document.getElementById('search-input').value.toLowerCase());
        }

        function moveNode(sourcePath, targetPath, dropPosition) {
            if (sourcePath === targetPath) return;
            if (targetPath.startsWith(sourcePath + '.')) return alert("Kann nicht in eigenes Kind-Element verschoben werden.");
            const sourceNodeClone = JSON.parse(JSON.stringify(getNodeByPath(sourcePath)));
            const { parent: sourceParent, index: sourceIndex } = getParentAndIndexByPath(sourcePath);
            sourceParent.splice(sourceIndex, 1);
            if (dropPosition === 'inside') { const targetNode = getNodeByPath(targetPath); if (!targetNode.children) targetNode.children = []; targetNode.children.push(sourceNodeClone); } 
            else {
                const { parent: targetParent, index: targetIndex } = getParentAndIndexByPath(targetPath);
                let insertIndex = targetIndex; if (dropPosition === 'after') insertIndex++;
                if (sourceParent === targetParent && sourceIndex < targetIndex) insertIndex--; 
                targetParent.splice(insertIndex, 0, sourceNodeClone);
            }
            buildMeshDetailsMap(getCurrentStructureData(), currentView); redrawTree(); debouncedSave();
        }

        function createTreeView(nodes, parentElement, numberPrefix = '', pathPrefix = '') { 
            const ul = document.createElement('ul'); 
            nodes.forEach((node, index) => { 
                const li = document.createElement('li'); li.classList.add('tree-item'); li.draggable = isDragSortMode;
                const currentNumber = numberPrefix + (index + 1); 
                const currentPath = pathPrefix ? `${pathPrefix}.children.${index}` : `${index}`; 
                li.dataset.path = currentPath; 

                const rowDiv = document.createElement('div'); rowDiv.classList.add('tree-row'); 
                const toggler = document.createElement('span'); toggler.classList.add('tree-toggler'); 
                const numberSpan = document.createElement('span'); numberSpan.classList.add('tree-number'); numberSpan.textContent = currentNumber; 
                const labelSpan = document.createElement('span'); labelSpan.classList.add('tree-label'); labelSpan.textContent = node.name; 

                // INFO ICON (NEU)
                const infoContainer = document.createElement('div');
                infoContainer.className = 'info-icon-container';
                if (node.hardware && node.hardware.length > 0) {
                    const infoIcon = document.createElement('span');
                    infoIcon.className = 'info-icon';
                    infoIcon.textContent = 'i';

                    // Tooltip Logic
                    infoIcon.addEventListener('mouseenter', (e) => {
                        const tooltip = document.getElementById('tree-tooltip');
                        let content = '';
                        if (node.hardware && node.hardware.length > 0) {
                            const cleanHardware = node.hardware.map(h => {
                                try {
                                    if (h.startsWith('{')) {
                                        return JSON.parse(h).label;
                                    }
                                } catch(e) {}
                                return h;
                            });
                            
                            content += `<strong>Hardware:</strong><ul>${cleanHardware.map(h => `<li>${h}</li>`).join('')}</ul>`;
                        }
                        tooltip.innerHTML = content;
                        tooltip.style.display = 'block';
                        updateTooltipPosition(e);
                    });
                    infoIcon.addEventListener('mousemove', updateTooltipPosition);
                    infoIcon.addEventListener('mouseleave', () => { document.getElementById('tree-tooltip').style.display = 'none'; });

                    infoContainer.appendChild(infoIcon);
                }

                const actionsDiv = document.createElement('div'); actionsDiv.classList.add('tree-actions'); 
                const editIcon = document.createElement('span'); editIcon.classList.add('action-icon', 'edit'); editIcon.innerHTML = '&#9998;'; editIcon.title = 'Eintrag bearbeiten'; editIcon.dataset.action = 'edit'; actionsDiv.appendChild(editIcon); 
                const addIcon = document.createElement('span'); addIcon.classList.add('action-icon', 'add'); addIcon.textContent = '+'; addIcon.title = 'Unterpunkt hinzufügen'; addIcon.dataset.action = 'add'; actionsDiv.appendChild(addIcon); 
                if (!node.children || node.children.length === 0) { const deleteIcon = document.createElement('span'); deleteIcon.classList.add('action-icon', 'delete'); deleteIcon.textContent = '×'; deleteIcon.title = 'Element löschen'; deleteIcon.dataset.action = 'delete'; actionsDiv.appendChild(deleteIcon); } 

                const wikiArrow = document.createElement('span');
                wikiArrow.classList.add('wiki-arrow', 'material-icons');
                wikiArrow.textContent = 'chevron_right';
                if (node.id) { wikiArrow.dataset.nodeId = node.id; wikiArrow.dataset.nodeNumber = currentNumber; }
                if (node.id && nodeHasWiki(node.id)) wikiArrow.classList.add('has-content');
                if (node.id && node.id === currentWikiNodeId) wikiArrow.classList.add('wiki-open');
                wikiArrow.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (!node.id) return;
                    if (currentWikiNodeId === node.id) { closeWikiPanel(); } else { openWikiPanel(node.id, node.name, currentNumber); }
                });

                const dragHandle = document.createElement('span'); dragHandle.classList.add('tree-drag-handle', 'material-icons'); dragHandle.textContent = 'drag_indicator'; dragHandle.title = 'Verschieben';
                rowDiv.appendChild(dragHandle); rowDiv.appendChild(toggler); rowDiv.appendChild(numberSpan); rowDiv.appendChild(labelSpan);
                rowDiv.appendChild(infoContainer); // INFO ICON
                rowDiv.appendChild(wikiArrow);
                rowDiv.appendChild(actionsDiv);
                li.appendChild(rowDiv);

                if (node.children && node.children.length > 0) { rowDiv.classList.add('tree-node'); createTreeView(node.children, li, currentNumber + '.', currentPath); } 
                else { toggler.classList.add('empty'); li.classList.add('tree-leaf');} 
                ul.appendChild(li); 
            }); 
            parentElement.appendChild(ul); 
        }

        function updateTooltipPosition(e) {
            const tooltip = document.getElementById('tree-tooltip');
            const x = e.clientX + 15;
            const y = e.clientY + 15;
            // Prevent going off screen
            if (y + tooltip.offsetHeight > window.innerHeight) tooltip.style.top = (e.clientY - tooltip.offsetHeight - 5) + 'px';
            else tooltip.style.top = y + 'px';
            tooltip.style.left = x + 'px';
        }

        function setupTreeEventListeners() { 
            const treeContainer = document.getElementById('tree'); 
            treeContainer.addEventListener('click', (event) => { 
                const row = event.target.closest('.tree-row'); if (!row) return; 
                const li = row.parentElement; const path = li.dataset.path; 
                const actionIcon = event.target.closest('.action-icon'); const toggler = event.target.closest('.tree-toggler'); 
                if (actionIcon) { 
                    event.stopPropagation(); const action = actionIcon.dataset.action; 
                    if (action === 'add') addNode(path); else if (action === 'delete') deleteNode(path); else if (action === 'edit') openStructureEditor(path); 
                } else if (toggler) { event.stopPropagation(); if (row.classList.contains('tree-node')) { li.classList.toggle('open'); } }
                else { flyToAndHighlight(path); if (currentWikiNodeId) { const n = getNodeByPath(path); if (n && n.id) { const num = li.querySelector('.wiki-arrow')?.dataset.nodeNumber || ''; openWikiPanel(n.id, n.name, num); } } }
            });
            treeContainer.addEventListener('dragstart', (e) => { if (!isDragSortMode) { e.preventDefault(); return; } const li = e.target.closest('.tree-item'); if(li) { draggedSourcePath = li.dataset.path; e.stopPropagation(); } });
            treeContainer.addEventListener('dragover', (e) => {
                e.preventDefault(); if (!isDragSortMode) return; const row = e.target.closest('.tree-row'); if(!row) return;
                const rect = row.getBoundingClientRect(); const relY = e.clientY - rect.top; const height = rect.height;
                row.classList.remove('drag-over-top', 'drag-over-bottom', 'drag-over-middle');
                document.querySelectorAll('.tree-row').forEach(el => { if(el !== row) el.classList.remove('drag-over-top', 'drag-over-bottom', 'drag-over-middle'); });
                if (relY < height * 0.25) { row.classList.add('drag-over-top'); row.dataset.dropPos = 'before'; } 
                else if (relY > height * 0.75) { row.classList.add('drag-over-bottom'); row.dataset.dropPos = 'after'; } 
                else { row.classList.add('drag-over-middle'); row.dataset.dropPos = 'inside'; }
            });
            treeContainer.addEventListener('dragleave', (e) => {});
            treeContainer.addEventListener('drop', (e) => {
                e.preventDefault(); if (!isDragSortMode) return; const row = e.target.closest('.tree-row');
                document.querySelectorAll('.tree-row').forEach(el => { el.classList.remove('drag-over-top', 'drag-over-bottom', 'drag-over-middle'); });
                if(row && draggedSourcePath) {
                    const li = row.parentElement; const targetPath = li.dataset.path; const dropPos = row.dataset.dropPos || 'inside';
                    if(targetPath !== draggedSourcePath) { moveNode(draggedSourcePath, targetPath, dropPos); }
                } draggedSourcePath = null;
            });
        }

        function setLabelViewState(labelDiv, isExpanded) {
            const { name, status, details, storageLink } = labelDiv.dataset;
            const bubbledCount = parseInt(labelDiv.dataset.bubbledCount || '0');
            let html = `<strong>${name}</strong>`;
            let dotsHtml = '';

            // A) Status-Punkt (Wartung/Issue)
            // Rot: Kritisch / Blocker
            if (status === 'heiss') {
                dotsHtml += '<span style="display:inline-block; width:10px; height:10px; background-color:#dc3545; border-radius:50%; margin-right:6px;" title="Kritischer Status"></span>';
            }
            // Blau: Aktive Arbeit (In Progress)
            else if (status === 'in_Betrieb') {
                dotsHtml += '<span style="display:inline-block; width:10px; height:10px; background-color:#0d6efd; border-radius:50%; margin-right:6px;" title="Wartung läuft"></span>';
            }
            // Grau Halbkreis: Wartung geplant
            else if (status === 'Wartung_geplant') {
                dotsHtml += '<span style="display:inline-block; width:8px; height:8px; border: 2px solid #6c757d; border-radius:50%; margin-right:6px;" title="Wartung in den nächsten 14 Tagen"></span>';
            }

            // B) Lager-Punkt (Lila)
            if (storageLink) {
                dotsHtml += '<span style="display:inline-block; width:10px; height:10px; background-color:#bebebe; border-radius:50%; margin-right:6px;" title="Lager/Rüstwagen vorhanden"></span>';
            }

            // C) Badge für gebubblte Kind-Aufträge
            if (bubbledCount > 0) {
                dotsHtml += `<span style="display:inline-flex; align-items:center; background:rgba(108,117,125,0.12); border:1px solid rgba(108,117,125,0.35); border-radius:8px; padding:0 5px; font-size:9px; color:#555; margin-right:6px;" title="${bubbledCount} Wartungsauftrag/-aufträge aus Unterelementen ohne 3D-Objekt">↓${bubbledCount}</span>`;
            }

            if (dotsHtml) {
                html += `<div style="margin-top:5px; line-height:1;">${dotsHtml}</div>`;
            }

            // 3. Erweiterte Ansicht (Details beim Aufklappen)
            if (isExpanded) {
                html += `<hr>`;
                html += `<div id="live-data-${labelDiv.id}" class="live-data-container" style="font-size:11px;">
                            <span style="color:#666; font-style:italic;">Lade Daten...</span>
                         </div>`;
                if (details && details !== 'null' && details !== '{}') {
                    const detailsObj = JSON.parse(details);
                    html += '<div style="margin-top:8px; border-top:1px dashed #eee; padding-top:4px;">';
                    for (const key in detailsObj) {
                        html += `<div class="detail-item" style="color:#888;"><strong>${key}:</strong> <span>${detailsObj[key]}</span></div>`;
                    }
                    html += '</div>';
                }

                labelDiv.classList.add('expanded');
                setTimeout(() => loadLiveDetails(name, `live-data-${labelDiv.id}`, labelDiv.dataset.bubbledJson || null), 10);

            } else {
                labelDiv.classList.remove('expanded');
            }

            labelDiv.innerHTML = html;
            labelDiv.isExpanded = isExpanded;
        }

        async function loadLiveDetails(unitName, containerId, bubbledJson = null) {
            const container = document.getElementById(containerId);
            if(!container) return;

            const labelDiv = document.querySelector(`.status-label[data-name="${unitName}"]`);
            let storageLink = null;
            if (labelDiv && labelDiv.dataset.storageLink) {
                storageLink = labelDiv.dataset.storageLink;
            }

            let html = '';
            let hasContent = false;

            try {
                // 1. Eigene Wartungsaufträge laden
                const response = await fetch(`/api/facility/unit_orders?unit=${encodeURIComponent(unitName)}`);
                const orders = await response.json();

                if (orders.length > 0) {
                    hasContent = true;
                    html += '<div style="margin-bottom:8px; font-weight:bold; color:#555;">Wartung & Issues</div>';
                    html += '<div style="display:flex; flex-direction:column; gap:6px;">';
                    orders.forEach(o => {
                        let icon = 'build'; let color = '#007bff';
                        if (o.type === 'I') {
                            icon = 'warning'; color = '#ffc107';
                            if (o.priority === 'High') color = '#fd7e14';
                            if (o.priority === 'Critical' || o.isBlocker) { icon = 'error'; color = '#dc3545'; }
                        }
                        const link = `/maintenance?openId=${o.id}`;
                        html += `
                            <a href="${link}" target="_blank" style="text-decoration:none; color:inherit; display:flex; align-items:start; gap:6px; background:rgba(255,255,255,0.5); padding:4px; border-radius:4px; transition:background 0.2s;" onmouseover="this.style.background='white'" onmouseout="this.style.background='rgba(255,255,255,0.5)'">
                                <span class="material-icons" style="font-size:14px; color:${color}; margin-top:2px;">${icon}</span>
                                <div>
                                    <div style="font-weight:600; font-size:11px; line-height:1.2;">${o.title}</div>
                                    <div style="font-size:9px; color:#666; font-family:monospace;">${o.id}</div>
                                </div>
                            </a>`;
                    });
                    html += '</div>';
                }

                // 2. Gebubblte Kind-Aufträge anzeigen
                const bubbledOrders = bubbledJson ? JSON.parse(bubbledJson) : [];
                if (bubbledOrders.length > 0) {
                    if (hasContent) html += '<hr style="border:0; border-top:1px dashed #ccc; margin:8px 0;">';
                    hasContent = true;
                    html += '<div style="margin-bottom:6px; font-weight:bold; color:#888; font-size:10px; text-transform:uppercase; letter-spacing:0.5px;">↓ Aufträge aus Unterelementen</div>';
                    html += '<div style="display:flex; flex-direction:column; gap:6px; opacity:0.9;">';
                    bubbledOrders.forEach(o => {
                        let icon = 'build'; let color = '#007bff';
                        if (o.type === 'I') {
                            icon = 'warning'; color = '#ffc107';
                            if (o.priority === 'High') color = '#fd7e14';
                            if (o.priority === 'Critical') { icon = 'error'; color = '#dc3545'; }
                        }
                        const link = `/maintenance?openId=${o.id}`;
                        html += `
                            <a href="${link}" target="_blank" style="text-decoration:none; color:inherit; display:flex; align-items:start; gap:6px; background:rgba(200,200,200,0.2); padding:4px; border-radius:4px; border-left:2px dashed #aaa; transition:background 0.2s;" onmouseover="this.style.background='rgba(200,200,200,0.4)'" onmouseout="this.style.background='rgba(200,200,200,0.2)'">
                                <span class="material-icons" style="font-size:14px; color:${color}; margin-top:2px;">${icon}</span>
                                <div>
                                    <div style="font-size:9px; color:#888; margin-bottom:1px;">${o.sourceName}</div>
                                    <div style="font-weight:600; font-size:11px; line-height:1.2;">${o.title || o.id}</div>
                                    <div style="font-size:9px; color:#666; font-family:monospace;">${o.id}</div>
                                </div>
                            </a>`;
                    });
                    html += '</div>';
                }

// 2. Lagerbestand laden (wenn verknüpft)
                if (storageLink) {
                    const storRes = await fetch(`/api/facility/storage_content?name=${encodeURIComponent(storageLink)}`);
                    const items = await storRes.json();
                    
                    if (items.length > 0) {
                        if (hasContent) html += '<hr style="border:0; border-top:1px dashed #ccc; margin:8px 0;">';
                        hasContent = true;
                        
                        html += `<div style="margin-bottom:4px; font-weight:bold; color:#004085;">📦 ${storageLink}</div>`;
                        
                        // Wrapper für horizontales Scrollen bei breiten Tabellen
                        html += '<div style="overflow-x:auto; max-width:100%;">';
                        html += '<table style="width:100%; font-size:10px; border-collapse:collapse; white-space:nowrap;">';
                        
                        // NEUE HEADER: Anz. | Name | P/N | S/N | Ebene | Box
                        html += `
                            <tr style="color:#888; text-align:left; font-size:9px; border-bottom:1px solid #ddd;">
                                <th style="padding:2px 4px;">Anz.</th>
                                <th style="padding:2px 4px;">Name</th>
                                <th style="padding:2px 4px;">P/N</th>
                                <th style="padding:2px 4px;">S/N</th>
                                <th style="padding:2px 4px;">Ebene</th>
                                <th style="padding:2px 4px;">Box</th>
                            </tr>`;
                        
                        items.forEach(item => {
                            const hwLink = `/test_equipment`;
                            const shortDesc = item.desc.length > 15 ? item.desc.substring(0, 15) + '...' : item.desc;

                            // Checkout-Indikator
                            let checkoutIcon = '';
                            if (item.checkedOut) {
                                const tip = (item.checkedOutReason
                                    ? `Ausgebucht von ${item.checkedOutBy} · ${item.checkedOutReason}`
                                    : `Ausgebucht von ${item.checkedOutBy}`).replace(/"/g, '&quot;');
                                checkoutIcon = `<span class="material-icons" title="${tip}" style="font-size:11px; color:#c05621; vertical-align:middle; margin-right:3px; cursor:help;">logout</span>`;
                            }

                            html += `
                                <tr style="border-bottom:1px solid #eee; ${item.checkedOut ? 'background:rgba(255,237,213,0.4);' : ''}">
                                    <td style="font-weight:bold; padding:2px 4px; text-align:center;">${item.qty}</td>

                                    <td style="padding:2px 4px;">
                                        ${checkoutIcon}<a href="${hwLink}" target="_blank" style="text-decoration:none; color:${item.checkedOut ? '#c05621' : '#007bff'}; font-weight:500;" title="${item.desc}">
                                            ${shortDesc}
                                        </a>
                                    </td>

                                    <td style="padding:2px 4px; font-family:monospace; color:#555;">${item.pn}</td>
                                    <td style="padding:2px 4px; font-family:monospace; color:#555;">${item.sn || '-'}</td>

                                    <td style="padding:2px 4px; text-align:center;">${item.level || '-'}</td>
                                    <td style="padding:2px 4px; text-align:center;">${item.box || '-'}</td>
                                </tr>
                            `;
                        });
                        html += '</table>';
                        html += '</div>'; // Ende Wrapper
                    }
                }
                
                if (!hasContent) {
                    container.innerHTML = '<span style="color:#28a745;">✓ Keine Meldungen / Lager leer</span>';
                } else {
                    container.innerHTML = html;
                }
                
            } catch (e) {
                console.error(e);
                container.innerHTML = '<span style="color:red;">Fehler beim Laden.</span>';
            }
        }
        function createStatusLabel(mesh, status, bubbledOrders = []) {
            const nodeData = getCurrentViewMaps().meshDetailsMap.get(mesh.name);
            if (!nodeData || nodeData.showStatusLabel === false) { return; }

            const labelDiv = document.createElement('div');
            labelDiv.id = `label-${Math.random().toString(36).substr(2, 9)}`;
            labelDiv.className = 'status-label';

            // Vorberechneten vollen Namen nutzen (Index + Name), Fallback auf normalen Namen
            const displayName = nodeData._runtime_fullName || nodeData.labelName || nodeData.name;

            labelDiv.dataset.name = displayName;
            labelDiv.dataset.status = status;
            labelDiv.dataset.details = nodeData.details ? JSON.stringify(nodeData.details) : null;
            if (nodeData.storageLink) { labelDiv.dataset.storageLink = nodeData.storageLink; }
            // Gebubblte Kind-Aufträge speichern (für Badge + Popup)
            if (bubbledOrders.length > 0) {
                labelDiv.dataset.bubbledCount = bubbledOrders.length;
                labelDiv.dataset.bubbledJson = JSON.stringify(bubbledOrders);
            }

            labelDiv.isExpanded = false;
            setLabelViewState(labelDiv, false);

            labelDiv.style.borderLeft = `5px solid ${(STATUS_COLORS[status] || STATUS_COLORS['N/A']).getStyle()}`;

            labelDiv.addEventListener('click', (event) => {
                event.stopPropagation();
                const path = getCurrentViewMaps().meshToPathMap.get(mesh.name);
                flyToAndHighlight(path);
                const wasExpanded = labelDiv.isExpanded;
                if (currentlyExpandedLabel && currentlyExpandedLabel !== labelDiv) {
                    setLabelViewState(currentlyExpandedLabel, false);
                }
                setLabelViewState(labelDiv, !wasExpanded);
                currentlyExpandedLabel = wasExpanded ? null : labelDiv;
                // If wiki panel is open, update it to this node
                if (currentWikiNodeId) {
                    const nd = getCurrentViewMaps().meshDetailsMap.get(mesh.name);
                    if (nd && nd.id && nd.id !== currentWikiNodeId) openWikiPanel(nd.id, nd.name, nd.number || '');
                }
            });

            const label = new CSS2DObject(labelDiv);
            label.userData = { meshName: mesh.name };
            const box = new THREE.Box3().setFromObject(mesh);
            const center = box.getCenter(new THREE.Vector3());
            label.position.copy(center);
            scene.add(label);
            allLabels.push(label);
        }

        async function updateAllLabels() { allLabels.forEach(label => { label.removeFromParent(); }); allLabels = []; allMaterials = []; await fetchRoomStatusAndUpdateColors(scene, false); if (!isStatusView) { allMaterials.forEach(item => { item.material.color.copy(NEUTRAL_COLOR); item.material.opacity = NEUTRAL_OPACITY; }); } }
        function loadModel() { 
            const loader = new GLTFLoader(); 
            const url = '/facility/model/current?t=' + new Date().getTime();
            loader.load(url, (gltf) => { scene.add(gltf.scene); gltf.scene.traverse(child => { if (child.isMesh) meshMap.set(child.name, child); }); availableMeshNames = Array.from(meshMap.keys()).sort(); fetchRoomStatusAndUpdateColors(gltf.scene); }, undefined, (error) => console.error('FEHLER:', error)); 
        }
        async function fetchRoomStatusAndUpdateColors(model, onlyUpdateLabels = false) { try { const response = await fetch('/api/facility/room_status'); const roomStatusData = await response.json(); const bubbledData = roomStatusData._bubbled || {}; model.traverse((child) => { if (child.isMesh) { const roomName = child.name; if (getCurrentViewMaps().meshDetailsMap.has(roomName)) { const status = roomStatusData[roomName] || 'N/A'; const color = STATUS_COLORS[status] || STATUS_COLORS['N/A']; if (!onlyUpdateLabels) { const material = createFresnelMaterial(color); allMaterials.push({ material: material, originalColor: color.clone() }); child.material = material; child.userData.originalMaterial = material; } createStatusLabel(child, status, bubbledData[roomName] || []); } else if (!onlyUpdateLabels) { child.material = new THREE.MeshStandardMaterial({ color: 0xadc5d9, transparent: true, opacity: 0.1, depthWrite: false }); child.userData.originalMaterial = child.material; } } }); } catch (error) { console.error('Fehler:', error); } }
        let currentWikiNodeId = null;

        const WIKI_TEMPLATES = {
            pruefstand: `# Technische Daten\n\n| Merkmal | Wert |\n|---------|------|\n| Medium | |\n| Leistung | |\n| Druckstufe | |\n| Baujahr | |\n\n# Schaltpläne & Dokumente\n\n# Wartungsintervalle\n\n| Intervall | Tätigkeit | Verantwortlich |\n|-----------|-----------|----------------|\n| Jährlich | | |\n\n# Abschalt- und Notfallprozedur\n\n1. \n2. \n\n# Notfallkontakte\n`,
            halle: `# Allgemein\n\n# Zugangsdaten & Schlüssel\n\n# Notausgänge & Gefahrenstellen\n\n# Verantwortliches Facility-Team\n\n# Besonderheiten\n`,
            allgemein: `# Beschreibung\n\n# Besonderheiten\n\n# Hinweise\n`
        };

        function updateStatusStyle() {
            const sel = document.getElementById('wiki-status');
            sel.className = 'status-' + sel.value;
        }

        function renderMarkdownPreview() {
            let src = document.getElementById('wiki-notizen').value;
            // Replace @[name](node:id) chips
            src = src.replace(/@\[([^\]]*)\]\(node:([^)]+)\)/g, (_, name, id) => {
                const node = getAllWikiNodes().find(n => n.id === id);
                const label = node ? (node.number ? node.number + ' ' + node.name : node.name) : name;
                return `<span class="wiki-node-ref" data-node-id="${id}"><span class="material-icons at-icon">location_on</span>${label}</span>`;
            });
            // Resolve attach: URLs before markdown parsing
            if (currentWikiNodeId) {
                const base = '/api/facility/attachments/' + currentWikiNodeId + '/';
                src = src.replace(/\(attach:([^)]+)\)/g, '(' + base + '$1)');
            }
            const preview = document.getElementById('wiki-preview');
            preview.innerHTML = window.marked ? marked.parse(src) : src.replace(/\\n/g, '<br>');
            // Node ref click handlers
            preview.querySelectorAll('.wiki-node-ref').forEach(el => {
                el.addEventListener('click', () => {
                    const nodeId = el.dataset.nodeId;
                    const node = getAllWikiNodes().find(n => n.id === nodeId);
                    if (node) navigateToWikiNode(node.id, node.name, node.number, node.source);
                });
            });
            // Inline images: lightbox; attachment links: download styling
            preview.querySelectorAll('img').forEach(img => {
                if (img.src.includes('/api/facility/attachments/')) {
                    img.addEventListener('click', () => openLightbox(img.src, img.alt));
                }
            });
            preview.querySelectorAll('a').forEach(a => {
                const href = a.getAttribute('href') || '';
                if (href.includes('/api/facility/attachments/')) {
                    a.classList.add('attach-link');
                    a.setAttribute('download', '');
                    a.innerHTML = '<span class="material-icons">attach_file</span>' + a.textContent;
                }
            });
        }

        let wikiIsEditMode = false;

        function setWikiMode(mode) {
            wikiIsEditMode = (mode === 'edit');
            const e = wikiIsEditMode;
            document.getElementById('wiki-edit-btn').style.display = e ? 'none' : '';
            document.getElementById('wiki-save-btn').style.display = e ? '' : 'none';
            document.getElementById('wiki-save-indicator').style.display = e ? '' : 'none';
            document.getElementById('wiki-meta-row').style.display = e ? '' : 'none';
            document.getElementById('wiki-view-meta').style.display = e ? 'none' : '';
            document.getElementById('wiki-template-row').style.display = e ? 'flex' : 'none';
            document.getElementById('wiki-notizen').style.display = e ? '' : 'none';
            document.getElementById('wiki-preview').style.display = e ? 'none' : 'block';
            document.getElementById('wiki-links-container').style.display = e ? '' : 'none';
            document.getElementById('wiki-view-links').style.display = e ? 'none' : '';
            document.getElementById('wiki-add-link-btn').style.display = e ? '' : 'none';
            document.getElementById('wiki-attach-upload-btn').style.display = e ? '' : 'none';
            document.querySelectorAll('.wiki-attach-del').forEach(btn => btn.style.display = e ? '' : 'none');
            if (!e) { renderMarkdownPreview(); updateWikiViewMeta(); updateWikiViewLinks(); }
        }

        function updateWikiViewMeta() {
            const status = document.getElementById('wiki-status').value;
            const bereich = document.getElementById('wiki-bereich').value;
            const verantw = document.getElementById('wiki-verantwortlicher').value.trim();
            const labels = { aktuell: '● Aktuell', in_bearbeitung: '◑ In Bearbeitung', veraltet: '○ Veraltet' };
            const statusEl = document.getElementById('wiki-view-status');
            statusEl.textContent = labels[status] || status;
            statusEl.className = `wiki-view-status-badge status-${status}`;
            const bereichEl = document.getElementById('wiki-view-bereich');
            bereichEl.textContent = bereich; bereichEl.style.display = bereich ? '' : 'none';
            const verEl = document.getElementById('wiki-view-verantwortlicher');
            verEl.textContent = verantw ? '👤 ' + verantw : ''; verEl.style.display = verantw ? '' : 'none';
        }

        function updateWikiViewLinks() {
            const container = document.getElementById('wiki-view-links');
            container.innerHTML = '';
            document.querySelectorAll('#wiki-links-container .wiki-link-row').forEach(row => {
                const label = row.querySelector('.wiki-link-label').value.trim();
                const url = row.querySelector('.wiki-link-url').value.trim();
                if (!label && !url) return;
                const a = document.createElement('a');
                a.href = url ? (url.startsWith('http') ? url : 'https://' + url) : '#';
                a.target = '_blank'; a.rel = 'noopener noreferrer';
                a.className = 'wiki-view-link';
                a.innerHTML = `<span class="material-icons" style="font-size:14px;">open_in_new</span>${label || url}`;
                container.appendChild(a);
            });
        }



        function formatAuditDate(iso) {
            if (!iso) return '';
            try { return new Date(iso).toLocaleString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit' }); }
            catch { return iso; }
        }

        // --- ÄNDERUNGSHISTORIE ---

        async function openHistoryPanel() {
            if (!currentWikiNodeId) return;
            const list = document.getElementById('wiki-history-list');
            list.innerHTML = '<div style="padding:20px;text-align:center;color:#bbb;font-size:13px;">Lade Verlauf…</div>';
            document.getElementById('wiki-history-overlay').classList.add('visible');
            try {
                const res = await fetch(`/api/facility/wiki/${currentWikiNodeId}/history`);
                const versions = await res.json();
                if (!versions.length) {
                    list.innerHTML = '<div style="padding:20px;text-align:center;color:#bbb;font-size:13px;">Noch kein Verlauf vorhanden.</div>';
                    return;
                }
                list.innerHTML = '';
                versions.forEach((v, i) => {
                    const entry = document.createElement('div');
                    entry.className = 'wiki-history-entry';
                    const preview = (v.notizen || '').replace(/^#+\s*/gm, '').trim().slice(0, 80) || '(leer)';
                    const statusLabel = { aktuell: '● Aktuell', in_bearbeitung: '◑ In Bearb.', veraltet: '○ Veraltet' }[v.status] || v.status || '';
                    entry.innerHTML = `
                        <div class="wiki-history-entry-meta">
                            <span class="wiki-history-entry-date">${formatAuditDate(v.geaendert_am)}</span>
                            <span class="wiki-history-entry-user">${v.geaendert_von || ''}</span>
                            <span class="wiki-history-entry-status">${statusLabel}</span>
                        </div>
                        <div class="wiki-history-entry-preview">${preview}</div>
                        <button class="wiki-history-restore-btn">In Editor laden</button>`;
                    entry.querySelector('.wiki-history-restore-btn').addEventListener('click', () => restoreVersion(v));
                    list.appendChild(entry);
                });
            } catch(e) {
                list.innerHTML = '<div style="padding:20px;text-align:center;color:#dc3545;font-size:13px;">Fehler beim Laden.</div>';
            }
        }

        function closeHistoryPanel() {
            document.getElementById('wiki-history-overlay').classList.remove('visible');
        }

        function restoreVersion(v) {
            document.getElementById('wiki-notizen').value = v.notizen || '';
            document.getElementById('wiki-status').value = v.status || 'aktuell';
            updateStatusStyle();
            document.getElementById('wiki-bereich').value = v.bereich || '';
            document.getElementById('wiki-verantwortlicher').value = v.verantwortlicher || '';
            const linksContainer = document.getElementById('wiki-links-container');
            linksContainer.innerHTML = '';
            (v.links || []).forEach(l => addWikiLinkRow(l.label, l.url));
            closeHistoryPanel();
            setWikiMode('edit');
            document.getElementById('wiki-save-indicator').style.opacity = '0';
        }

        // --- VOLLTEXTSUCHE ---

        function getAllWikiNodes() {
            const results = [];
            function traverse(nodes, prefix, source) {
                nodes.forEach((n, i) => {
                    const num = prefix + (i + 1);
                    results.push({ id: n.id, name: n.name, number: num, source });
                    if (n.children) traverse(n.children, num + '.', source);
                });
            }
            traverse(allStructureData.betriebseinheiten || [], '', 'betriebseinheiten');
            traverse(allStructureData.gebaeude || [], '', 'gebaeude');
            (allStructureData.custom_layers || []).forEach(layer => {
                traverse(allStructureData[layer.id] || [], '', layer.id);
            });
            return results;
        }

        // ── AT-MENTION NODE PICKER ──────────────────────────────────────
        let atPickerActive = false;
        let atPickerNodes = [];
        let atPickerIndex = -1;

        function showAtPicker(nodes, query, textarea) {
            const picker = document.getElementById('wiki-at-picker');
            const q = query.toLowerCase();
            atPickerNodes = nodes;
            atPickerIndex = nodes.length > 0 ? 0 : -1;
            picker.innerHTML = '';
            if (!nodes.length) {
                picker.innerHTML = '<div class="wiki-at-empty">Keine Nodes gefunden</div>';
            } else {
                nodes.forEach((node, i) => {
                    const item = document.createElement('div');
                    item.className = 'wiki-at-item' + (i === 0 ? ' active' : '');
                    const hl = (t) => {
                        if (!query) return t;
                        const idx = t.toLowerCase().indexOf(q);
                        if (idx === -1) return t;
                        return t.substring(0, idx) + '<mark>' + t.substring(idx, idx + query.length) + '</mark>' + t.substring(idx + query.length);
                    };
                    item.innerHTML = `<span class="wiki-at-item-num">${node.number || ''}</span><span class="wiki-at-item-name">${hl(node.name)}</span>`;
                    item.addEventListener('mousedown', (e) => { e.preventDefault(); selectAtNode(node, textarea); });
                    picker.appendChild(item);
                });
            }
            // Position: prefer above textarea, fall back to below
            const rect = textarea.getBoundingClientRect();
            picker.style.display = 'block';
            requestAnimationFrame(() => {
                const pickerH = picker.offsetHeight;
                if (rect.top - pickerH - 6 >= 0) {
                    picker.style.top = (rect.top - pickerH - 6) + 'px';
                } else {
                    picker.style.top = (rect.bottom + 4) + 'px';
                }
                picker.style.left = rect.left + 'px';
                picker.style.width = rect.width + 'px';
            });
            atPickerActive = true;
        }

        function hideAtPicker() {
            document.getElementById('wiki-at-picker').style.display = 'none';
            atPickerActive = false;
            atPickerNodes = [];
            atPickerIndex = -1;
        }

        function setAtPickerIndex(idx) {
            const items = document.querySelectorAll('#wiki-at-picker .wiki-at-item');
            items.forEach((el, i) => el.classList.toggle('active', i === idx));
            atPickerIndex = idx;
            if (items[idx]) items[idx].scrollIntoView({ block: 'nearest' });
        }

        function selectAtNode(node, textarea) {
            const pos = textarea.selectionStart;
            const text = textarea.value;
            const before = text.substring(0, pos);
            const lastAt = before.lastIndexOf('@');
            if (lastAt === -1) return;
            const label = (node.number ? node.number + ' ' : '') + node.name;
            const insertion = `@[${label}](node:${node.id})`;
            textarea.value = text.substring(0, lastAt) + insertion + text.substring(pos);
            const newPos = lastAt + insertion.length;
            textarea.selectionStart = textarea.selectionEnd = newPos;
            hideAtPicker();
            textarea.focus();
        }

        function checkAtTrigger(textarea) {
            const pos = textarea.selectionStart;
            const before = textarea.value.substring(0, pos);
            const lastAt = before.lastIndexOf('@');
            if (lastAt === -1) { hideAtPicker(); return; }
            // Only trigger when @ is at start or after whitespace
            const charBefore = lastAt > 0 ? before[lastAt - 1] : ' ';
            if (/\\w/.test(charBefore)) { hideAtPicker(); return; }
            const query = before.substring(lastAt + 1);
            // No newlines in the query
            if (query.includes('\\n')) { hideAtPicker(); return; }
            const q = query.toLowerCase();
            const matches = getAllWikiNodes()
                .filter(n => n.id && (n.name.toLowerCase().includes(q) || (n.number && n.number.startsWith(q))))
                .slice(0, 8);
            showAtPicker(matches, query, textarea);
        }
        // ────────────────────────────────────────────────────────────────

        // ── SLASH COMMAND PALETTE ────────────────────────────────────────
        const SLASH_COMMANDS = [
            { id: 'image',  icon: 'image',            label: 'Bild hochladen',   group: 'Medien',       accept: 'image/*' },
            { id: 'file',   icon: 'attach_file',      label: 'Datei hochladen',  group: 'Medien',       accept: '*' },
            { id: 'node',   icon: 'location_on',      label: 'Node verlinken',   group: 'Medien' },
            { id: 'h1',     icon: 'title',            label: 'Überschrift 1',    group: 'Format',       insert: '# ' },
            { id: 'h2',     icon: 'title',            label: 'Überschrift 2',    group: 'Format',       insert: '## ' },
            { id: 'h3',     icon: 'title',            label: 'Überschrift 3',    group: 'Format',       insert: '### ' },
            { id: 'table',  icon: 'table_chart',      label: 'Tabelle',          group: 'Format',       insert: '| Spalte 1 | Spalte 2 |\\n|---|---|\\n| Wert | Wert |\\n' },
            { id: 'hr',     icon: 'horizontal_rule',  label: 'Trennlinie',       group: 'Format',       insert: '\\n---\\n' },
            { id: 'code',   icon: 'code',             label: 'Code-Block',       group: 'Format',       insert: '```\\n\\n```' },
            { id: 'quote',  icon: 'format_quote',     label: 'Zitat',            group: 'Format',       insert: '> ' },
        ];

        let slashActive = false;
        let slashItems = [];
        let slashIndex = -1;
        let slashAtPos = -1;

        function showSlashPalette(textarea, query) {
            const q = query.toLowerCase();
            const palette = document.getElementById('wiki-slash-palette');
            const filtered = SLASH_COMMANDS.filter(c => !q || c.label.toLowerCase().includes(q) || c.group.toLowerCase().includes(q));
            slashItems = filtered;
            slashIndex = filtered.length > 0 ? 0 : -1;
            palette.innerHTML = '';
            if (!filtered.length) {
                palette.innerHTML = '<div class="slash-empty">Kein Befehl gefunden</div>';
            } else {
                let lastGroup = '';
                filtered.forEach((cmd, i) => {
                    if (cmd.group !== lastGroup) {
                        const gl = document.createElement('div');
                        gl.className = 'slash-group-label';
                        gl.textContent = cmd.group;
                        palette.appendChild(gl);
                        lastGroup = cmd.group;
                    }
                    const item = document.createElement('div');
                    item.className = 'slash-item' + (i === 0 ? ' active' : '');
                    item.dataset.index = i;
                    item.innerHTML = `<span class="material-icons">${cmd.icon}</span><span class="slash-item-label">${cmd.label}</span>`;
                    item.addEventListener('mousedown', (e) => { e.preventDefault(); executeSlashCommand(cmd, textarea); });
                    palette.appendChild(item);
                });
            }
            const rect = textarea.getBoundingClientRect();
            palette.style.display = 'block';
            requestAnimationFrame(() => {
                const ph = palette.offsetHeight;
                palette.style.top = (rect.top - ph - 6 >= 0 ? rect.top - ph - 6 : rect.bottom + 4) + 'px';
                palette.style.left = rect.left + 'px';
                palette.style.width = rect.width + 'px';
            });
            slashActive = true;
        }

        function hideSlashPalette() {
            document.getElementById('wiki-slash-palette').style.display = 'none';
            slashActive = false;
            slashItems = [];
            slashIndex = -1;
        }

        function setSlashIndex(idx) {
            const items = document.querySelectorAll('#wiki-slash-palette .slash-item');
            items.forEach((el, i) => el.classList.toggle('active', i === idx));
            slashIndex = idx;
            if (items[idx]) items[idx].scrollIntoView({ block: 'nearest' });
        }

        function executeSlashCommand(cmd, textarea) {
            // Remove the /query from textarea
            const pos = textarea.selectionStart;
            const text = textarea.value;
            const before = text.substring(0, pos);
            const lastSlash = before.lastIndexOf('/');
            const prefix = lastSlash >= 0 ? text.substring(0, lastSlash) : text.substring(0, pos);
            const suffix = text.substring(pos);
            hideSlashPalette();
            if (cmd.id === 'image' || cmd.id === 'file') {
                textarea.value = prefix + suffix;
                textarea.selectionStart = textarea.selectionEnd = lastSlash >= 0 ? lastSlash : pos;
                triggerFileUpload(cmd.accept, textarea, cmd.id === 'image');
            } else if (cmd.id === 'node') {
                textarea.value = prefix + '@' + suffix;
                textarea.selectionStart = textarea.selectionEnd = (lastSlash >= 0 ? lastSlash : pos) + 1;
                checkAtTrigger(textarea);
                textarea.focus();
            } else if (cmd.insert !== undefined) {
                const ins = cmd.insert.replace(/\\n/g, '\\n');
                textarea.value = prefix + ins + suffix;
                textarea.selectionStart = textarea.selectionEnd = prefix.length + ins.length;
                textarea.focus();
            }
        }

        function checkSlashTrigger(textarea) {
            const pos = textarea.selectionStart;
            const before = textarea.value.substring(0, pos);
            const lastSlash = before.lastIndexOf('/');
            if (lastSlash === -1) { hideSlashPalette(); return; }
            const charBefore = lastSlash > 0 ? before[lastSlash - 1] : '\\n';
            if (charBefore !== '\\n' && charBefore !== ' ' && charBefore !== '') { hideSlashPalette(); return; }
            const query = before.substring(lastSlash + 1);
            if (query.includes('\\n') || query.length > 20) { hideSlashPalette(); return; }
            slashAtPos = lastSlash;
            showSlashPalette(textarea, query);
        }
        // ─────────────────────────────────────────────────────────────────

        // ── ATTACHMENTS ──────────────────────────────────────────────────
        let currentAttachments = [];

        function getMimeIcon(mime, name) {
            if (!mime) mime = '';
            if (mime.startsWith('image/')) return 'image';
            if (mime === 'application/pdf') return 'picture_as_pdf';
            if (mime.includes('word') || name.endsWith('.docx') || name.endsWith('.doc')) return 'description';
            if (mime.includes('excel') || mime.includes('spreadsheet') || name.endsWith('.xlsx') || name.endsWith('.xls') || name.endsWith('.csv')) return 'table_chart';
            if (name.endsWith('.stp') || name.endsWith('.step') || name.endsWith('.dxf') || name.endsWith('.dwg') || name.endsWith('.igs')) return 'view_in_ar';
            if (mime.startsWith('video/')) return 'videocam';
            return 'attach_file';
        }

        function formatFileSize(bytes) {
            if (!bytes) return '';
            if (bytes < 1024) return bytes + ' B';
            if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
            return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
        }

        function renderAttachmentList(attachments) {
            currentAttachments = attachments ? attachments.slice() : [];
            const list = document.getElementById('wiki-attachments-list');
            list.innerHTML = '';
            currentAttachments.forEach((att, idx) => {
                const icon = getMimeIcon(att.mime, att.original_name || att.name);
                const row = document.createElement('div');
                row.className = 'wiki-attach-item';
                row.innerHTML = `<span class="material-icons wiki-attach-icon">${icon}</span>
                    <span class="wiki-attach-name" title="${(att.original_name || att.name).replace(/"/g, '&quot;')}">${att.original_name || att.name}</span>
                    <span class="wiki-attach-size">${formatFileSize(att.size)}</span>
                    <button class="wiki-attach-del material-icons" title="Löschen" style="display:${wikiIsEditMode ? '' : 'none'}">delete</button>`;
                row.querySelector('.wiki-attach-name').addEventListener('click', () => {
                    window.open('/api/facility/attachments/' + currentWikiNodeId + '/' + att.name, '_blank');
                });
                row.querySelector('.wiki-attach-del').addEventListener('click', async () => {
                    if (!confirm('Anhang "' + (att.original_name || att.name) + '" löschen?')) return;
                    try {
                        await fetch('/api/facility/wiki/' + currentWikiNodeId + '/attachments/' + att.name, { method: 'DELETE' });
                        currentAttachments.splice(idx, 1);
                        renderAttachmentList(currentAttachments);
                        // Remove inline references from textarea
                        const ta = document.getElementById('wiki-notizen');
                        if (ta) {
                            const esc = att.name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');
                            ta.value = ta.value
                                .replace(new RegExp('!\\\\[[^\\\\]]*\\\\]\\\\(attach:' + esc + '\\\\)', 'g'), '')
                                .replace(new RegExp('\\\\[[^\\\\]]*\\\\]\\\\(attach:' + esc + '\\\\)', 'g'), '');
                        }
                    } catch(e) { console.error(e); }
                });
                list.appendChild(row);
            });
        }

        function triggerFileUpload(accept, textarea, isImage) {
            const input = document.getElementById('wiki-file-input');
            input.accept = accept || '*';
            input.onchange = async () => {
                const files = [...input.files];
                input.value = '';
                if (!files.length || !currentWikiNodeId) return;
                const indicator = document.getElementById('wiki-upload-indicator');
                const label = document.getElementById('wiki-upload-label');
                indicator.classList.add('visible');
                for (const file of files) {
                    label.textContent = 'Lädt hoch: ' + file.name;
                    const fd = new FormData();
                    fd.append('file', file);
                    try {
                        const res = await fetch('/api/facility/wiki/' + currentWikiNodeId + '/attachments', { method: 'POST', body: fd });
                        const data = await res.json();
                        if (data.success) {
                            currentAttachments.push({ name: data.name, original_name: data.original_name, size: data.size, mime: data.mime, uploaded_by: data.uploaded_by, uploaded_at: data.uploaded_at });
                            renderAttachmentList(currentAttachments);
                            if (textarea) {
                                const isImg = data.mime && data.mime.startsWith('image/');
                                const snippet = isImg
                                    ? '![' + data.original_name + '](attach:' + data.name + ')'
                                    : '[' + data.original_name + '](attach:' + data.name + ')';
                                const curPos = textarea.selectionStart;
                                textarea.value = textarea.value.substring(0, curPos) + snippet + textarea.value.substring(curPos);
                                textarea.selectionStart = textarea.selectionEnd = curPos + snippet.length;
                                textarea.focus();
                            }
                        }
                    } catch(err) { console.error('Upload fehlgeschlagen:', err); }
                }
                indicator.classList.remove('visible');
            };
            input.click();
        }

        function openLightbox(src, alt) {
            const lb = document.getElementById('wiki-lightbox');
            document.getElementById('wiki-lightbox-img').src = src;
            document.getElementById('wiki-lightbox-img').alt = alt || '';
            lb.classList.add('open');
        }

        function closeLightbox() {
            document.getElementById('wiki-lightbox').classList.remove('open');
        }
        // ─────────────────────────────────────────────────────────────────

        // ── WARTUNGSAUFTRÄGE + LAGER ─────────────────────────────────────
        function toggleInfoSection(sectionEl) {
            sectionEl.classList.toggle('open');
        }

        function updateInfoZones() {
            const orders  = document.getElementById('wiki-orders-section');
            const storage = document.getElementById('wiki-storage-section');
            const zones   = document.getElementById('wiki-info-zones');
            const hasContent = (orders && orders.style.display !== 'none') ||
                               (storage && storage.style.display !== 'none');
            zones.classList.toggle('has-content', hasContent);
        }

        function setHeaderBadge(maxLevel) {
            const badge = document.getElementById('wiki-header-status-badge');
            badge.className = '';
            if (maxLevel >= 1) {
                badge.classList.add('level-' + maxLevel);
                const labels = { 3: 'Anlage gesperrt', 2: 'Kritische Wartung', 1: 'Aktive Aufträge' };
                badge.title = labels[maxLevel] || 'Aktive Aufträge';
            }
        }

        function renderOrdersSection(orders) {
            const section = document.getElementById('wiki-orders-section');
            const list    = document.getElementById('wiki-orders-list');
            const count   = document.getElementById('wiki-orders-count');
            if (!orders || !orders.length) { section.style.display = 'none'; setHeaderBadge(0); updateInfoZones(); return; }
            const maxLevel = Math.max(...orders.map(o => o.level || 0));
            setHeaderBadge(maxLevel);
            count.textContent = orders.length + ' Auftrag' + (orders.length > 1 ? '\\u00e4ge' : '');
            const dotColors = { 3: 'level-3', 2: 'level-2', 1: 'level-1', 0: 'level-0' };
            list.innerHTML = orders.map(o => {
                const dot   = `<span class="wiki-order-dot ${dotColors[o.level] || 'level-0'}"></span>`;
                const type  = `<span class="wiki-order-type type-${o.type}">${o.type === 'I' ? 'Issue' : 'Wartung'}</span>`;
                const meta  = o.date ? `<span class="wiki-order-meta">${o.date}</span>` : '';
                return `<a class="wiki-order-row" href="/maintenance?openId=${encodeURIComponent(o.id)}" target="_blank" title="${(o.title||'').replace(/"/g,'&quot;')}">${dot}${type}<span class="wiki-order-title">${o.title || '(kein Titel)'}</span>${meta}</a>`;
            }).join('');
            section.style.display = '';
            if (wikiPanelState !== 'normal') section.classList.add('open');
            updateInfoZones();
        }

        function renderStorageSection(items, storageName) {
            const section = document.getElementById('wiki-storage-section');
            const list    = document.getElementById('wiki-storage-list');
            const nameEl  = document.getElementById('wiki-storage-name');
            if (!items || !items.length) { section.style.display = 'none'; updateInfoZones(); return; }
            nameEl.textContent = storageName;
            const checkedOut = items.filter(i => i.checkedOut).length;
            list.innerHTML = items.map(i => {
                const loc = [i.level ? 'Eb.' + i.level : '', i.box ? 'Box ' + i.box : ''].filter(Boolean).join(' ');
                const co  = i.checkedOut ? `<span class="wiki-storage-item-co" title="Ausgecheckt von: ${i.checkedOutBy || '?'}">↗ ${i.checkedOutBy || 'ausgecheckt'}</span>` : '';
                return `<div class="wiki-storage-item${i.checkedOut ? ' checked-out' : ''}">
                    <span class="wiki-storage-item-desc" title="${(i.desc||'').replace(/"/g,'&quot;')}">${i.desc || '—'}</span>
                    <span class="wiki-storage-item-pn">${i.pn !== '-' ? i.pn : ''}</span>
                    <span class="wiki-storage-item-qty">${i.qty}×</span>
                    ${loc ? `<span class="wiki-storage-item-loc">${loc}</span>` : ''}
                    ${co}
                </div>`;
            }).join('');
            if (checkedOut > 0) nameEl.textContent += ' · ' + checkedOut + ' ausgecheckt';
            section.style.display = '';
            if (wikiPanelState !== 'normal') section.classList.add('open');
            updateInfoZones();
            if (pendingFocusStorage) {
                pendingFocusStorage = false;
                const hlq = pendingFocusStorageQuery;
                pendingFocusStorageQuery = '';
                section.classList.add('open');
                setTimeout(() => {
                    section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                    if (hlq) {
                        const rows = list.querySelectorAll('.wiki-storage-item');
                        let firstMatch = null;
                        rows.forEach(row => {
                            const text = row.textContent.toLowerCase();
                            if (text.includes(hlq)) {
                                row.classList.add('search-highlight');
                                if (!firstMatch) firstMatch = row;
                            }
                        });
                        if (firstMatch) firstMatch.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                    }
                }, 80);
            }
        }

        async function loadWikiSideData(nodeId) {
            // Reset
            document.getElementById('wiki-orders-section').style.display = 'none';
            document.getElementById('wiki-orders-section').classList.remove('open');
            document.getElementById('wiki-storage-section').style.display = 'none';
            document.getElementById('wiki-storage-section').classList.remove('open');
            document.getElementById('wiki-info-zones').classList.remove('has-content');
            setHeaderBadge(0);

            // Wartungsaufträge
            fetch('/api/facility/wiki/' + nodeId + '/orders')
                .then(r => r.json())
                .then(orders => renderOrdersSection(orders))
                .catch(() => {});

            // Lager (storageLink aus allStructureData)
            let node = findNodeById(allStructureData.betriebseinheiten || [], nodeId)
                    || findNodeById(allStructureData.gebaeude || [], nodeId);
            if (!node) { for (const _cl of allStructureData.custom_layers || []) { node = findNodeById(allStructureData[_cl.id] || [], nodeId); if (node) break; } }
            if (node && node.storageLink) {
                fetch('/api/facility/storage_content?name=' + encodeURIComponent(node.storageLink))
                    .then(r => r.json())
                    .then(items => renderStorageSection(items, node.storageLink))
                    .catch(() => {});
            }
        }
        // ─────────────────────────────────────────────────────────────────

        async function loadStorageSearchIndex() {
            if (storageSearchIndex !== null) return storageSearchIndex;
            try {
                const res = await fetch('/api/facility/storage_index');
                storageSearchIndex = res.ok ? await res.json() : {};
            } catch(e) { storageSearchIndex = {}; }
            return storageSearchIndex;
        }

        function buildStorageNodeMap() {
            // Returns Map<storageName, {nodeId, nodeName, nodeNumber, nodeSource}>
            const map = new Map();
            function traverse(nodes, source) {
                (nodes || []).forEach(n => {
                    if (n.storageLink) map.set(n.storageLink, { nodeId: n.id, nodeName: n.name, nodeNumber: n.number || '', nodeSource: source });
                    if (n.children) traverse(n.children, source);
                });
            }
            traverse(allStructureData.betriebseinheiten || [], 'betriebseinheiten');
            traverse(allStructureData.gebaeude || [], 'gebaeude');
            (allStructureData.custom_layers || []).forEach(layer => {
                traverse(allStructureData[layer.id] || [], layer.id);
            });
            return map;
        }

        async function performWikiSearch(query) {
            const container = document.getElementById('wiki-search-results');
            const q = query.trim().toLowerCase();
            if (!q) { container.innerHTML = ''; return; }

            const highlight = (text, term) => {
                const idx = text.toLowerCase().indexOf(term);
                if (idx < 0) return text;
                return text.slice(0, idx) + '<mark>' + text.slice(idx, idx + term.length) + '</mark>' + text.slice(idx + term.length);
            };

            // --- Wiki hits ---
            const hits = []; // { node, excerpt, type }
            const nodes = getAllWikiNodes();
            nodes.forEach(node => {
                if (!node.id) return;
                const data = wikiData[node.id];
                let excerpt = '';
                let matched = false;
                const nameMatch = node.name.toLowerCase().includes(q);
                if (data) {
                    const notizen = (data.notizen || '').toLowerCase();
                    const bereich = (data.bereich || '').toLowerCase();
                    const verantw = (data.verantwortlicher || '').toLowerCase();
                    const linkText = (data.links || []).map(l => l.label + ' ' + l.url).join(' ').toLowerCase();
                    if (notizen.includes(q) || bereich.includes(q) || verantw.includes(q) || linkText.includes(q) || nameMatch) {
                        matched = true;
                        const idx = notizen.indexOf(q);
                        if (idx >= 0) {
                            const start = Math.max(0, idx - 20);
                            excerpt = (start > 0 ? '…' : '') + (data.notizen || '').slice(start, idx + q.length + 40).replace(/^#+\\s*/gm, '');
                        } else if (bereich.includes(q)) {
                            excerpt = 'Bereich: ' + data.bereich;
                        } else if (verantw.includes(q)) {
                            excerpt = 'Verantwortlich: ' + data.verantwortlicher;
                        }
                    }
                } else if (nameMatch) {
                    matched = true;
                }
                if (matched) hits.push({ node, excerpt, type: 'wiki' });
            });

            // --- Storage hits ---
            const storageIndex = await loadStorageSearchIndex();
            const nodeMap = buildStorageNodeMap();
            const seenStorage = new Set();
            nodeMap.forEach((nodeInfo, storageName) => {
                if (seenStorage.has(storageName)) return;
                const items = storageIndex[storageName] || [];
                const matchedItem = items.find(it =>
                    (it.desc || '').toLowerCase().includes(q) ||
                    (it.pn  || '').toLowerCase().includes(q) ||
                    (it.sn  || '').toLowerCase().includes(q)
                );
                if (matchedItem) {
                    seenStorage.add(storageName);
                    const excerptParts = [];
                    if ((matchedItem.desc || '').toLowerCase().includes(q)) excerptParts.push(matchedItem.desc);
                    else if ((matchedItem.pn || '').toLowerCase().includes(q)) excerptParts.push('PN: ' + matchedItem.pn);
                    else excerptParts.push('SN: ' + matchedItem.sn);
                    hits.push({
                        node: { id: nodeInfo.nodeId, name: nodeInfo.nodeName, number: nodeInfo.nodeNumber, source: nodeInfo.nodeSource },
                        excerpt: excerptParts[0] || '',
                        type: 'storage',
                        storageName
                    });
                }
            });

            if (!hits.length) {
                container.innerHTML = '<div id="wiki-search-empty">Keine Treffer</div>';
                return;
            }
            container.innerHTML = '';
            hits.slice(0, 12).forEach(({ node, excerpt, type, storageName }) => {
                const el = document.createElement('div');
                el.className = 'wiki-search-result';
                const displayName = node.number ? `${node.number} ${node.name}` : node.name;
                const badge = type === 'storage'
                    ? `<span class="wiki-search-type-badge wiki-search-type-storage">Lager</span>`
                    : '';
                const excHtml = excerpt ? `<div class="wiki-search-result-excerpt">${highlight(excerpt.slice(0, 80), query.trim())}</div>` : '';
                el.innerHTML = `<div class="wiki-search-result-name">${highlight(displayName, query.trim())}${badge}</div>${excHtml}`;
                el.addEventListener('click', () => { navigateToWikiNode(node.id, node.name, node.number, node.source, type === 'storage', type === 'storage' ? q : ''); closeWikiSearch(); });
                container.appendChild(el);
            });
        }

        let pendingFocusStorage = false;
        let pendingFocusStorageQuery = '';

        function navigateToWikiNode(nodeId, nodeName, nodeNumber, nodeSource, focusStorage = false, focusStorageQuery = '') {
            if (!document.getElementById('treeview-container').classList.contains('visible')) {
                document.getElementById('treeview-container').classList.add('visible');
            }
            if (appMode !== 'structure') { appMode = 'structure'; switchSidebarView('structure'); clearFlowView(); }
            if (nodeSource && currentView !== nodeSource) switchToView(nodeSource);
            const arrow = document.querySelector(`.wiki-arrow[data-node-id="${nodeId}"]`);
            if (arrow) {
                const treeItem = arrow.closest('.tree-item');
                const path = treeItem?.dataset.path;
                if (path) {
                    flyToAndHighlight(path);
                } else {
                    let parent = treeItem?.parentElement?.closest('.tree-item');
                    while (parent) { parent.classList.add('open'); parent = parent.parentElement?.closest('.tree-item'); }
                    treeItem?.scrollIntoView({ behavior: 'smooth', block: 'center' });
                }
            }
            pendingFocusStorage = focusStorage;
            pendingFocusStorageQuery = focusStorageQuery;
            openWikiPanel(nodeId, nodeName, nodeNumber);
        }

        function openWikiSearch() {
            document.getElementById('wiki-search-popup').classList.add('open');
            setTimeout(() => document.getElementById('wiki-search-input').focus(), 50);
        }

        function closeWikiSearch() {
            document.getElementById('wiki-search-popup').classList.remove('open');
            document.getElementById('wiki-search-input').value = '';
            document.getElementById('wiki-search-results').innerHTML = '';
        }

        function markWikiArrow(nodeId) {
            document.querySelectorAll('.wiki-arrow.wiki-open').forEach(a => a.classList.remove('wiki-open'));
            if (nodeId) {
                const arrow = document.querySelector(`.wiki-arrow[data-node-id="${nodeId}"]`);
                if (arrow) arrow.classList.add('wiki-open');
            }
        }

        function openWikiPanel(nodeId, nodeName, nodeNumber) {
            currentWikiNodeId = nodeId;
            markWikiArrow(nodeId);
            const arrow = document.querySelector(`.wiki-arrow[data-node-id="${nodeId}"]`);
            const num = nodeNumber || arrow?.dataset.nodeNumber || '';
            document.getElementById('wiki-panel-title').textContent = num ? `${num} ${nodeName}` : nodeName;
            const data = wikiData[nodeId] || {};
            document.getElementById('wiki-notizen').value = data.notizen || '';
            document.getElementById('wiki-status').value = data.status || 'aktuell';
            updateStatusStyle();
            document.getElementById('wiki-bereich').value = data.bereich || '';
            document.getElementById('wiki-verantwortlicher').value = data.verantwortlicher || '';
            document.getElementById('wiki-template-select').value = '';
            const linksContainer = document.getElementById('wiki-links-container');
            linksContainer.innerHTML = '';
            (data.links || []).forEach(link => addWikiLinkRow(link.label, link.url));
            renderAttachmentList(data.attachments || []);
            loadWikiSideData(nodeId);
            setWikiMode('view');
            const auditEl = document.getElementById('wiki-audit-info');
            if (data.geaendert_von) {
                auditEl.innerHTML = `Geändert: <strong>${data.geaendert_von}</strong><br>${formatAuditDate(data.geaendert_am)}`;
            } else if (data.erstellt_von) {
                auditEl.innerHTML = `Erstellt: <strong>${data.erstellt_von}</strong><br>${formatAuditDate(data.erstellt_am)}`;
            } else {
                auditEl.innerHTML = '';
            }
            document.getElementById('wiki-panel').classList.add('visible');
            document.getElementById('wiki-save-indicator').style.opacity = '0';
        }

        // ── WIKI PANEL SIZE ──────────────────────────────────────────────
        let wikiPanelState = 'normal';

        function cycleWikiPanelSize() {
            const panel = document.getElementById('wiki-panel');
            const tree  = document.getElementById('treeview-container');
            const icon  = document.querySelector('#wiki-expand-btn .material-icons');
            const btn   = document.getElementById('wiki-expand-btn');
            if (wikiPanelState === 'normal') {
                wikiPanelState = 'expanded';
                panel.classList.add('expanded');
                panel.classList.remove('focus');
                tree.classList.remove('wiki-focus-hidden');
                icon.textContent = 'fullscreen';
                btn.title = 'Vollbild (Tree ausblenden)';
            } else if (wikiPanelState === 'expanded') {
                wikiPanelState = 'focus';
                panel.classList.remove('expanded');
                panel.classList.add('focus');
                tree.classList.add('wiki-focus-hidden');
                icon.textContent = 'fullscreen_exit';
                btn.title = 'Normal';
            } else {
                wikiPanelState = 'normal';
                panel.classList.remove('expanded', 'focus');
                tree.classList.remove('wiki-focus-hidden');
                icon.textContent = 'open_in_full';
                btn.title = 'Erweitern';
            }

        }

        function resetWikiPanelSize() {
            wikiPanelState = 'normal';
            document.getElementById('wiki-panel').classList.remove('expanded', 'focus');
            const tree = document.getElementById('treeview-container');
            if (tree) tree.classList.remove('wiki-focus-hidden');
            const icon = document.querySelector('#wiki-expand-btn .material-icons');
            if (icon) { icon.textContent = 'open_in_full'; document.getElementById('wiki-expand-btn').title = 'Erweitern'; }
        }
        // ─────────────────────────────────────────────────────────────────

        function closeWikiPanel() {
            document.getElementById('wiki-panel').classList.remove('visible');
            resetWikiPanelSize();
            hideAtPicker();
            markWikiArrow(null);
            currentWikiNodeId = null;
        }

        function addWikiLinkRow(label = '', url = '') {
            const container = document.getElementById('wiki-links-container');
            const row = document.createElement('div');
            row.className = 'wiki-link-row';
            row.innerHTML = `
                <input type="text" placeholder="Bezeichnung" value="${label.replace(/"/g, '&quot;')}" class="wiki-link-label">
                <input type="text" placeholder="URL oder Pfad" value="${url.replace(/"/g, '&quot;')}" class="wiki-link-url">
                <span class="material-icons wiki-link-open" title="Öffnen">open_in_new</span>
                <span class="wiki-link-del" title="Entfernen">×</span>`;
            row.querySelector('.wiki-link-open').addEventListener('click', () => { const u = row.querySelector('.wiki-link-url').value.trim(); if (u) window.open(u.startsWith('http') ? u : 'https://' + u, '_blank'); });
            row.querySelector('.wiki-link-del').addEventListener('click', () => row.remove());
            container.appendChild(row);
        }

        async function saveWikiData() {
            if (!currentWikiNodeId) return;
            const payload = {
                notizen: document.getElementById('wiki-notizen').value,
                status: document.getElementById('wiki-status').value,
                bereich: document.getElementById('wiki-bereich').value,
                verantwortlicher: document.getElementById('wiki-verantwortlicher').value.trim(),
                links: [...document.querySelectorAll('#wiki-links-container .wiki-link-row')].map(row => ({
                    label: row.querySelector('.wiki-link-label').value.trim(),
                    url: row.querySelector('.wiki-link-url').value.trim()
                })).filter(l => l.label || l.url),
                attachments: currentAttachments.slice()
            };
            try {
                const res = await fetch(`/api/facility/wiki/${currentWikiNodeId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
                const result = await res.json();
                wikiData[currentWikiNodeId] = { ...payload, ...result.meta };
                const auditEl = document.getElementById('wiki-audit-info');
                if (result.meta) auditEl.innerHTML = `Geändert: <strong>${result.meta.geaendert_von}</strong><br>${formatAuditDate(result.meta.geaendert_am)}`;
                redrawTree();
                markWikiArrow(currentWikiNodeId);
                setWikiMode('view');
                const ind = document.getElementById('wiki-save-indicator');
                ind.style.opacity = '1';
                setTimeout(() => { ind.style.opacity = '0'; }, 2500);
            } catch(e) { console.error('Wiki-Speichern fehlgeschlagen', e); }
        }

        function toggleViewMode() { isStatusView = !isStatusView; if (isStatusView) { allLabels.forEach(label => label.visible = true); clearFlowView(); allMaterials.forEach(item => { item.material.color.copy(item.originalColor); item.material.opacity = 0.8; }); } else { allLabels.forEach(label => label.visible = false); clearFlowView(); allMaterials.forEach(item => { item.material.color.copy(NEUTRAL_COLOR); item.material.opacity = NEUTRAL_OPACITY; }); } if (highlightedObjects.length > 0) { highlightObjects([]); document.querySelectorAll('.tree-row.active').forEach(el => el.classList.remove('active')); } updateLayersPanelState(); }

        // ── CUSTOM LAYERS ─────────────────────────────────────────────────
        const LAYER_ICONS = ['category','electrical_services','science','water_drop','thermostat','safety_divider','hub','route','factory','engineering','fire_extinguisher','sensor_window'];

        function renderCustomLayerOptions() {
            const container = document.getElementById('custom-layers-container');
            if (!container) return;
            container.innerHTML = '';
            (allStructureData.custom_layers || []).forEach(layer => {
                const div = document.createElement('div');
                div.className = 'layers-option' + (appMode === 'structure' && currentView === layer.id ? ' active' : '');
                div.id = 'layer-opt-' + layer.id;
                const deleteBtn = IS_ADMIN_OR_PLANNER
                    ? `<span class="material-icons" data-delete-layer="${layer.id}" title="Layer löschen" style="font-size:16px;color:#ccc;margin-left:4px;cursor:pointer;flex-shrink:0;" onmouseover="this.style.color='#dc3545'" onmouseout="this.style.color='#ccc'">delete_outline</span>`
                    : '';
                div.innerHTML = `<span class="material-icons layers-opt-icon">${layer.icon || 'category'}</span><span class="layers-opt-label">${layer.name}</span>${deleteBtn}<span class="material-icons layers-opt-check">check</span>`;
                div.addEventListener('click', (e) => {
                    if (e.target.dataset && e.target.dataset.deleteLayer) { deleteCustomLayer(e.target.dataset.deleteLayer); return; }
                    if (appMode === 'processes') toggleAppMode();
                    if (currentView !== layer.id) switchToView(layer.id);
                    document.getElementById('layers-popup').classList.remove('open');
                });
                container.appendChild(div);
            });
        }

        function openCreateLayerDialog() {
            let selectedIcon = LAYER_ICONS[0];
            const picker = document.getElementById('new-layer-icon-picker');
            picker.innerHTML = '';
            LAYER_ICONS.forEach(icon => {
                const btn = document.createElement('div');
                btn.style.cssText = 'width:36px;height:36px;border:2px solid #ddd;border-radius:6px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all 0.1s;';
                btn.innerHTML = `<span class="material-icons" style="font-size:20px;color:#555;">${icon}</span>`;
                if (icon === selectedIcon) { btn.style.borderColor = '#007bff'; btn.style.background = '#e8f0fe'; }
                btn.addEventListener('click', () => {
                    selectedIcon = icon;
                    picker.querySelectorAll('div').forEach(b => { b.style.borderColor = '#ddd'; b.style.background = ''; });
                    btn.style.borderColor = '#007bff'; btn.style.background = '#e8f0fe';
                });
                picker.appendChild(btn);
            });
            document.getElementById('new-layer-name').value = '';
            document.getElementById('new-layer-create').onclick = () => {
                const name = document.getElementById('new-layer-name').value.trim();
                if (!name) { document.getElementById('new-layer-name').focus(); return; }
                createCustomLayer(name, selectedIcon);
                closeCreateLayerDialog();
            };
            document.getElementById('new-layer-dialog').style.display = 'block';
            document.getElementById('new-layer-overlay').style.display = 'block';
            setTimeout(() => document.getElementById('new-layer-name').focus(), 50);
        }

        function closeCreateLayerDialog() {
            document.getElementById('new-layer-dialog').style.display = 'none';
            document.getElementById('new-layer-overlay').style.display = 'none';
        }

        function createCustomLayer(name, icon) {
            if (!allStructureData.custom_layers) allStructureData.custom_layers = [];
            const id = 'layer_' + generateUUID().replace(/-/g, '').slice(0, 12);
            allStructureData.custom_layers.push({ id, name, icon });
            allStructureData[id] = [];
            viewMaps[id] = { meshToPathMap: new Map(), meshDetailsMap: new Map() };
            renderCustomLayerOptions();
            saveStructure();
        }

        function deleteCustomLayer(id) {
            if (!confirm('Layer und alle darin enthaltenen Daten löschen?')) return;
            allStructureData.custom_layers = (allStructureData.custom_layers || []).filter(l => l.id !== id);
            delete allStructureData[id];
            delete viewMaps[id];
            if (currentView === id) switchToView('betriebseinheiten');
            renderCustomLayerOptions();
            saveStructure();
        }
        // ─────────────────────────────────────────────────────────────────

        function updateLayersPanelState() {
            document.getElementById('layer-opt-betriebseinheiten').classList.toggle('active', appMode === 'structure' && currentView === 'betriebseinheiten');
            document.getElementById('layer-opt-gebaeude').classList.toggle('active', appMode === 'structure' && currentView === 'gebaeude');
            document.getElementById('layer-opt-prozesse').classList.toggle('active', appMode === 'processes');
            document.getElementById('layer-opt-status').classList.toggle('active', isStatusView);
            (allStructureData.custom_layers || []).forEach(layer => {
                const el = document.getElementById('layer-opt-' + layer.id);
                if (el) el.classList.toggle('active', appMode === 'structure' && currentView === layer.id);
            });
            const isDefault = (appMode === 'structure' && currentView === 'betriebseinheiten' && isStatusView);
            document.getElementById('layers-btn').classList.toggle('has-custom', !isDefault);
        }
        init();
    </script>

    <!-- CUSTOM LAYER CREATION DIALOG -->
    <div id="new-layer-overlay" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.3); z-index:999;"></div>
    <div id="new-layer-dialog" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); z-index:1000; background:#fff; border-radius:12px; box-shadow:0 8px 30px rgba(0,0,0,0.2); padding:24px; width:340px; border:1px solid #ddd;">
        <h3 style="margin:0 0 16px 0; font-size:16px; color:#333;">Neuer Layer</h3>
        <div style="margin-bottom:12px;">
            <label style="display:block; font-size:13px; color:#555; margin-bottom:4px; font-weight:500;">Name</label>
            <input id="new-layer-name" type="text" placeholder="z.B. Prüfstände" style="width:100%; box-sizing:border-box; padding:8px 10px; border:1px solid #ccc; border-radius:6px; font-size:14px;">
        </div>
        <div style="margin-bottom:20px;">
            <label style="display:block; font-size:13px; color:#555; margin-bottom:8px; font-weight:500;">Icon</label>
            <div id="new-layer-icon-picker" style="display:flex; flex-wrap:wrap; gap:8px;"></div>
        </div>
        <div style="display:flex; gap:8px; justify-content:flex-end;">
            <button id="new-layer-cancel" style="padding:8px 16px; border:1px solid #ccc; background:#fff; border-radius:6px; cursor:pointer; font-size:14px;">Abbrechen</button>
            <button id="new-layer-create" style="padding:8px 16px; background:#007bff; color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:14px; font-weight:500;">Erstellen</button>
        </div>
    </div>
</body>
</html>
"""

TEMPLATE_EDITOR = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ultimativer 3D Layout-Editor mit Baugruppen (Parenting)</title>
    <script src="https://unpkg.com/lucide@latest"></script>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <style>
        :root {
            --panel-bg: rgba(255, 255, 255, 0.95); --panel-blur: 12px;
            --border-color: rgba(0, 0, 0, 0.1); --shadow-color: rgba(0, 0, 0, 0.15);
            --text-color: #333; --primary-color: #007bff; --scene-bg: #f0f2f5;
        }
        body { margin: 0; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background-color: var(--scene-bg); color: var(--text-color); }
        #canvas-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; }
        .panel { background: var(--panel-bg); backdrop-filter: blur(var(--panel-blur)); border: 1px solid var(--border-color); border-radius: 12px; box-shadow: 0 8px 30px var(--shadow-color); padding: 15px; box-sizing: border-box; z-index: 10; }
        #left-panel-container {
            position: fixed; top: 74px; left: 20px; z-index: 20;
            display: flex; flex-direction: column; gap: 10px;
            width: 260px;
            max-height: calc(100vh - 94px);
            overflow-y: auto;
            transform: translateX(-120%); opacity: 0;
            transition: transform 0.3s ease, opacity 0.3s ease;
            pointer-events: none;
        }
        #left-panel-container.visible { transform: translateX(0); opacity: 1; pointer-events: auto; }
        #left-panel-container::-webkit-scrollbar { width: 6px; }
        #left-panel-container::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 3px; }
        #control-buttons { position: fixed; top: 20px; left: 20px; z-index: 30; }
        .control-btn { width: 44px; height: 44px; background-color: #fff; border: 1px solid #ddd; border-radius: 8px; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; }
        .control-btn:hover { background-color: #f8f8f8; transform: translateY(-2px); }
        .control-btn[disabled] { cursor: not-allowed; opacity: 0.5; background-color: #f5f5f5; }
        .control-btn[disabled]:hover { transform: none; }
        .control-btn svg { width: 24px; height: 24px; color: #555; }
        #alignment-panel { position: fixed; top: 20px; right: 20px; width: 280px; display: none; }
        .panel-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; }
        .panel-header h2 { margin: 0; font-size: 16px; color: var(--text-color); border-bottom: none; padding-bottom: 0; }
        .panel-header .chevron { transition: transform 0.3s ease; }
        .panel-content { max-height: 2000px; overflow: hidden; transition: max-height 0.4s ease-out, margin-top 0.4s ease-out; margin-top: 15px; }
        .panel.collapsed .panel-content { max-height: 0; margin-top: 0; }
        .panel.collapsed .panel-header .chevron { transform: rotate(-90deg); }
        .panel h3 { font-size: 14px; margin: 0 0 15px 0; color: var(--text-color); border-bottom: 1px solid #eee; padding-bottom: 10px; }
        .button-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)); gap: 8px; margin-bottom: 15px; }
        .btn { padding: 8px 12px; border: 1px solid #ccc; background-color: #fff; color: var(--text-color); border-radius: 6px; cursor: pointer; font-weight: 500; transition: all 0.2s ease; text-align: center; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 12px; }
        .btn:hover { background-color: #f0f0f0; border-color: #999; }
        .btn[disabled] { cursor: not-allowed; opacity: 0.5; background-color: #f5f5f5; }
        .btn.active { background-color: var(--primary-color); color: white; border-color: var(--primary-color); }
        .btn-fullwidth { grid-column: 1 / -1; }
        .btn-success { background-color: #28a745; border-color: #28a745; color: white; }
        .btn-primary-outline { background-color: transparent; border-color: var(--primary-color); color: var(--primary-color); }
        .btn-primary-outline:hover { background-color: rgba(0, 123, 255, 0.1); }
        .btn svg { width: 16px; height: 16px; }
        .form-group { margin-bottom: 15px; }
        .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 5px; color: #555; }
        .form-group input { width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; box-sizing: border-box; text-align: right; }
        #info-text { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.7); color: white; padding: 10px 20px; border-radius: 8px; font-size: 14px; pointer-events: none; opacity: 0; transition: opacity 0.3s; }
        #info-text.visible { opacity: 1; }

        .align-info { font-size: 13px; margin-bottom: 10px; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; padding-bottom: 5px; }
        .align-info span { font-weight: bold; }
        .name-primary { color: #cccc00; text-shadow: 0 0 1px #333; }
        .name-target { color: #ff9900; text-shadow: 0 0 1px #333;}

        .align-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; }
        .align-grid label { grid-column: 1 / -1; font-size: 11px; font-weight: 700; text-transform: uppercase; color: #888; margin-top: 10px; margin-bottom: 2px; }
        .align-grid .btn { padding: 6px 0; min-width: 0; justify-content: center; }
        .align-grid .btn svg { width: 18px; height: 18px; color: #555; }
        .align-grid .btn:hover svg { color: #000; }
        .align-grid .btn svg.lucide-circle { width: 14px; height: 14px; fill: #555; }

        #tree-list, #tree-list ul { list-style: none; padding: 0; margin: 0; }
        #tree-list { max-height: 300px; overflow-y: auto; }
        #tree-list ul { padding-left: 20px; }
        .tree-item { display: flex; align-items: center; padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; position: relative; }
        .tree-item:hover { background-color: #f0f0f0; }
        .tree-item.active { background-color: var(--primary-color); color: white; }
        .tree-item.locked { color: #999; font-style: italic; }
        .tree-item-label { flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; }
        .tree-item-label input { width: 100%; background: #fff; border: 1px solid var(--primary-color); color: black; border-radius: 4px; padding: 2px 4px; font-size: 14px; }
        .tree-item-icon { margin-right: 6px; color: #aaa; }
        .tree-item-icon svg { width: 14px; height: 14px; }
        .tree-item-actions { display: flex; align-items: center; margin-left: auto; }
        .btn-icon { background: none; border: none; cursor: pointer; opacity: 0.6; padding: 2px 4px; border-radius: 4px; display: flex; align-items: center; justify-content: center;}
        .btn-icon:hover { opacity: 1; background-color: rgba(0,0,0,0.1); }
        .btn-icon.btn-delete-item { color: #dc3545; }
        .btn-icon.btn-lock.locked { color: var(--primary-color); opacity: 1; }
        .btn-icon svg { width: 16px; height: 16px; }
        .dimension-inputs { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }

        /* BACK BTN STYLING */
        #back-to-viewer-btn { position: fixed; top: 20px; left: 74px; width: 44px; height: 44px; background-color: #333; color: white; border: 1px solid #000; border-radius: 8px; cursor: pointer; z-index: 30; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); font-size: 20px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; text-decoration: none; }
        #back-to-viewer-btn:hover { background-color: #555; transform: translateY(-2px); }

        /* STARTUP MODAL */
        #startup-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); backdrop-filter: blur(5px); z-index: 100; display: flex; align-items: center; justify-content: center; }
        .modal-card { background: white; padding: 30px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); max-width: 400px; text-align: center; }
        .modal-card h2 { margin-top: 0; color: #333; }
        .modal-card p { color: #666; line-height: 1.5; margin-bottom: 25px; }
        .modal-actions { display: flex; gap: 10px; justify-content: center; }
        .btn-large { padding: 12px 24px; font-size: 16px; cursor: pointer; border-radius: 6px; border: none; font-weight: 600; transition: transform 0.1s; }
        .btn-large:hover { transform: translateY(-2px); }
        .btn-large-primary { background-color: #007bff; color: white; box-shadow: 0 4px 12px rgba(0,123,255,0.3); }
        .btn-large-secondary { background-color: #f0f0f0; color: #555; }
        #back-to-viewer-btn { 
            position: fixed; 
            top: 20px; 
            left: 74px; /* Rechts neben dem Toolbox-Button */
            width: 44px; 
            height: 44px; 
            background-color: #fff; 
            color: #555; 
            border: 1px solid #ddd; 
            border-radius: 8px; 
            cursor: pointer; 
            z-index: 30; 
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); 
            display: flex; 
            align-items: center; 
            justify-content: center; 
            transition: all 0.2s ease; 
            text-decoration: none; 
        }
        #back-to-viewer-btn:hover { 
            background-color: #f8f8f8; 
            transform: translateY(-2px); 
            color: #007bff; 
        }
    </style>
</head>
<body>
    <div id="canvas-container"></div>
    <input type="file" id="glb-file-input" style="display: none;" accept=".glb">

    <div id="control-buttons">
        <button id="toggle-toolbox-btn" class="control-btn" title="Werkzeuge ein-/ausblenden"><i data-lucide="sliders-horizontal"></i></button>
    </div>

    <!-- LINK ZURÜCK ZUR VIEW -->
    <a href="{{ url_for('serve_facility_viewer') }}" id="back-to-viewer-btn" title="Zurück zur Ansicht">
        <span class="material-icons">visibility</span>
    </a>

    <!-- STARTUP MODAL -->
    <div id="startup-modal">
        <div class="modal-card">
            <h2>Editor Starten</h2>
            <p>Möchtest du das aktuelle Live-Modell vom Server laden, um es zu bearbeiten?</p>
            <div class="modal-actions">
                <button class="btn-large btn-large-primary" id="btn-modal-load-server">Ja, Modell laden</button>
                <button class="btn-large btn-large-secondary" id="btn-modal-empty">Nein, leere Szene</button>
            </div>
        </div>
    </div>

    <div id="left-panel-container">
        <div id="toolbox-panel" class="panel">
            <div class="panel-header"><h2>Toolbox</h2><i data-lucide="chevron-down" class="chevron"></i></div>
            <div class="panel-content">
                <div class="form-group"> <label>Verlauf</label> <div class="button-group" style="grid-template-columns: 1fr 1fr;"> <button id="btn-undo" class="btn" title="Letzte Aktion rückgängig machen (Strg+Z)"><i data-lucide="undo-2"></i>Zurück</button> <button id="btn-redo" class="btn" title="Letzte Aktion wiederholen (Strg+Y)"><i data-lucide="redo-2"></i>Wiederh.</button> </div> </div>
                <div class="form-group"> <label>Neues Objekt</label> <div class="button-group"> <button id="btn-add-box" class="btn"><i data-lucide="box"></i>Würfel</button> <button id="btn-add-sphere" class="btn"><i data-lucide="circle"></i>Kugel</button> <button id="btn-add-cylinder" class="btn"><i data-lucide="cylinder"></i>Zyl.</button> </div> </div>
                <div class="form-group"> <label>Modus (Q, W, E, R)</label> <div class="button-group" style="grid-template-columns: 1fr 1fr 1fr 1fr;"> 
                    <button id="btn-select" class="btn" title="Auswählen (Q)"><i data-lucide="mouse-pointer-2"></i></button>
                    <button id="btn-translate" class="btn active" title="Verschieben (W)"><i data-lucide="move"></i></button> 
                    <button id="btn-rotate" class="btn" title="Rotieren (E)"><i data-lucide="rotate-3d"></i></button> 
                    <button id="btn-scale" class="btn" title="Skalieren (R)"><i data-lucide="scale"></i></button> 
                </div> </div>
                <div class="form-group" id="boolean-operations-panel" style="display: none;">
                    <label>Boolesche Operationen</label>
                    <div class="button-group" style="grid-template-columns: 1fr 1fr;"> <button id="btn-union" class="btn" title="Beide Objekte zu einem verbinden"><i data-lucide="unite"></i>Vereinigen</button> <button id="btn-subtract" class="btn" title="Zielobjekt vom primären Objekt abziehen"><i data-lucide="minus-square"></i>Subtrahieren</button> <button id="btn-intersect" class="btn btn-fullwidth" title="Nur den überlappenden Teil behalten"><i data-lucide="intersect"></i>Schnittmenge</button> </div>
                </div>
                <div class="form-group"> <label for="grid-size-input">Gitter & Snap Größe</label> <input type="number" id="grid-size-input" value="1" step="0.1" min="0.1"> </div>

                <div class="form-group">
                    <label>Import / Export</label>
                    <input type="text" id="export-filename" placeholder="Dateiname (z.B. raum1)" style="margin-bottom: 8px;">
                    <div class="button-group" style="grid-template-columns: 1fr 1fr;">
                        <button id="btn-load-glb" class="btn btn-primary-outline"><i data-lucide="upload"></i>Laden</button>
                        <button id="btn-export-glb" class="btn btn-secondary"><i data-lucide="download"></i>DL</button>
                    </div>
                    <!-- SAVE TO SERVER BUTTON -->
                    <button id="btn-save-server" class="btn btn-success btn-fullwidth" style="margin-top: 5px;"><i data-lucide="cloud-upload"></i>Auf Server speichern (Live)</button>

                    <div id="loaded-file-info" style="font-size: 11px; color: #666; margin-top: 5px; text-align: center; font-style: italic;">Keine Datei geladen</div>
                </div>
            </div>
        </div>
        <div id="scene-tree-panel" class="panel">
            <div class="panel-header"><h2>Szenen-Objekte</h2><i data-lucide="chevron-down" class="chevron"></i></div>
            <div class="panel-content">
                <ul id="tree-list"></ul>
                <div id="properties-panel" style="display: none;">
                    <h3>Eigenschaften</h3>
                    <div class="form-group"> <label>Größe (X, Y, Z)</label> <div class="dimension-inputs"> <input type="number" id="prop-size-x" step="0.1" placeholder="X"> <input type="number" id="prop-size-y" step="0.1" placeholder="Y"> <input type="number" id="prop-size-z" step="0.1" placeholder="Z"> </div> </div>
                    <div class="form-group" style="margin-top: 15px;"> <button id="btn-interior-mode" class="btn btn-fullwidth"><i data-lucide="scan"></i>Innenraum-Modus</button> </div>
                </div>
            </div>
        </div>
    </div>

    <div id="alignment-panel" class="panel">
        <h2>Ausrichten</h2>
        <div class="align-info"><span>Bewegt:</span> <span id="align-primary-name" class="name-primary"></span></div>
        <div class="align-info"><span>Ziel:</span> <span id="align-target-name" class="name-target"></span></div>

        <div class="align-grid">
            <label>X (Links/Rechts)</label>
            <button class="btn" id="align-x-min-outer" title="Links neben Ziel"><i data-lucide="chevrons-left"></i></button> 
            <button class="btn" id="align-x-min-inner" title="Links bündig"><i data-lucide="chevron-left"></i></button> 
            <button class="btn" id="align-x-center" title="Zentriert"><i data-lucide="circle"></i></button> 
            <button class="btn" id="align-x-max-inner" title="Rechts bündig"><i data-lucide="chevron-right"></i></button> 
            <button class="btn" id="align-x-max-outer" title="Rechts neben Ziel"><i data-lucide="chevrons-right"></i></button>

            <label>Y (Unten/Oben)</label>
            <button class="btn" id="align-y-min-outer" title="Unter Ziel"><i data-lucide="chevrons-down"></i></button> 
            <button class="btn" id="align-y-min-inner" title="Unten bündig"><i data-lucide="chevron-down"></i></button> 
            <button class="btn" id="align-y-center" title="Zentriert"><i data-lucide="circle"></i></button> 
            <button class="btn" id="align-y-max-inner" title="Oben bündig"><i data-lucide="chevron-up"></i></button> 
            <button class="btn" id="align-y-max-outer" title="Auf Ziel"><i data-lucide="chevrons-up"></i></button>

            <label>Z (Tiefe)</label>
            <button class="btn" id="align-z-min-outer" title="Vor Ziel (Kameranäher)"><i data-lucide="chevrons-down"></i></button> 
            <button class="btn" id="align-z-min-inner" title="Vorne bündig"><i data-lucide="chevron-down"></i></button> 
            <button class="btn" id="align-z-center" title="Zentriert"><i data-lucide="circle"></i></button> 
            <button class="btn" id="align-z-max-inner" title="Hinten bündig"><i data-lucide="chevron-up"></i></button> 
            <button class="btn" id="align-z-max-outer" title="Hinter Ziel (Kameraferner)"><i data-lucide="chevrons-up"></i></button>
        </div>
    </div>
    <div id="info-text"></div>

    <script type="importmap">{ "imports": { "three": "https://unpkg.com/three@0.158.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/", "three-csg-ts": "https://esm.sh/three-csg-ts" } }</script>
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { TransformControls } from 'three/addons/controls/TransformControls.js';
        import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        import { CSG } from 'three-csg-ts';

        let scene, camera, renderer, orbitControls, transformControls, raycaster, pointer;
        let primarySelection = null, targetSelection = null;
        let editableObjects = [];
        let gridHelper, gridSize = 1;
        let axisScene, axisCamera; const axisElements = [];
        let targetPosition = null, targetLookAt = null, isAnimatingCamera = false;
        let isScaling = false; let preTransformData = {};
        let isShiftDown = false;
        let historyStack = []; let redoStack = [];
        let isInteriorMode = false; let interiorModeObject = null;
        let originalGridSize = 1;
        let floorplanMesh = null;
        let interiorGridHelper = null;
        let isRestoringState = false;
        let gizmoEnabled = true;

        const defaultMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.2, roughness: 0.6, transparent: true, opacity: 0.85 });
        const primaryHighlightMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00, emissive: 0x333300 });
        const targetHighlightMaterial = new THREE.MeshStandardMaterial({ color: 0xffa500, emissive: 0x332200 });
        const ui = { alignmentPanel: document.getElementById('alignment-panel'), booleanOperationsPanel: document.getElementById('boolean-operations-panel'), treeList: document.getElementById('tree-list'), leftPanelContainer: document.getElementById('left-panel-container'), infoText: document.getElementById('info-text'), propertiesPanel: document.getElementById('properties-panel'), propSizeX: document.getElementById('prop-size-x'), propSizeY: document.getElementById('prop-size-y'), propSizeZ: document.getElementById('prop-size-z'), toggleToolboxBtn: document.getElementById('toggle-toolbox-btn'), gridSizeInput: document.getElementById('grid-size-input'), btnSaveServer: document.getElementById('btn-save-server'),  undoBtn: document.getElementById('btn-undo'), redoBtn: document.getElementById('btn-redo'), btnInteriorMode: document.getElementById('btn-interior-mode'), btnLoadGlb: document.getElementById('btn-load-glb'), glbFileInput: document.getElementById('glb-file-input'), exportFilename: document.getElementById('export-filename'), loadedFileInfo: document.getElementById('loaded-file-info') };

        function init() {
            scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f2f5);
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(8, 10, 15); camera.lookAt(0,0,0);
            const container = document.getElementById('canvas-container');
            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            container.appendChild(renderer.domElement);
            scene.add(new THREE.AmbientLight(0xffffff, 0.8));
            scene.add(new THREE.DirectionalLight(0xffffff, 1.0).position.set(10, 20, 5));
            orbitControls = new OrbitControls(camera, renderer.domElement); orbitControls.enableDamping = true;
            transformControls = new TransformControls(camera, renderer.domElement); scene.add(transformControls);
            transformControls.setTranslationSnap(gridSize); transformControls.setRotationSnap(THREE.MathUtils.degToRad(15)); transformControls.setScaleSnap(0.25);
            raycaster = new THREE.Raycaster(); pointer = new THREE.Vector2();
            updateGrid(); initAxisHelper(); setupEventListeners(); animate();
            lucide.createIcons();

            initModalLogic();

            window.addEventListener('beforeunload', (e) => { e.preventDefault(); e.returnValue = ''; });
        }

        // --- STARTUP MODAL LOGIC ---
        function initModalLogic() {
            const modal = document.getElementById('startup-modal');
            const btnLoad = document.getElementById('btn-modal-load-server');
            const btnEmpty = document.getElementById('btn-modal-empty');

            btnLoad.addEventListener('click', () => {
                modal.style.display = 'none';
                showInfoText("Lade Modell vom Server...", 5000);
                loadGLBFromUrl('/facility/model/current?t=' + Date.now(), "Live-Modell");
            });

            btnEmpty.addEventListener('click', () => {
                modal.style.display = 'none';
                showInfoText("Leere Szene gestartet.", 3000);
                saveState();
            });
        }

        function loadGLBFromUrl(url, displayName) {
            const loader = new GLTFLoader();
            loader.load(url, (gltf) => {
                processLoadedScene(gltf, displayName);
            }, undefined, (err) => {
                console.error(err);
                showInfoText("Fehler: Konnte Modell nicht laden (existiert es?).", 5000);
            });
        }

        function saveState() {
            if (isRestoringState) return;
            const state = editableObjects.map(obj => ({
                uuid: obj.uuid,
                parentUuid: obj.parent !== scene ? obj.parent.uuid : null,
                geometry: obj.geometry.toJSON(),
                position: obj.position.clone(),
                rotation: obj.rotation.clone(),
                scale: obj.scale.clone(),
                name: obj.name,
                userData: JSON.parse(JSON.stringify(obj.userData)) 
            }));
            historyStack.push(state);
            redoStack = [];
            updateHistoryButtons();
        }

        function restoreState(state) {
            isRestoringState = true; 
            if (isInteriorMode) exitInteriorMode();
            selectObject(null, null);
            transformControls.detach();
            while (editableObjects.length > 0) { removeObjectAndChildren(editableObjects[0]); } 

            const geometryLoader = new THREE.BufferGeometryLoader();
            const objectMap = new Map();

            for (const objData of state) {
                const geometry = geometryLoader.parse(objData.geometry);
                const mesh = new THREE.Mesh(geometry, defaultMaterial.clone());
                mesh.uuid = objData.uuid; 
                mesh.position.copy(objData.position);
                mesh.rotation.copy(objData.rotation);
                mesh.scale.copy(objData.scale);
                mesh.name = objData.name;
                mesh.userData = JSON.parse(JSON.stringify(objData.userData));
                mesh.userData.originalMaterial = mesh.material;
                editableObjects.push(mesh);
                objectMap.set(mesh.uuid, mesh);
            }

            for (const objData of state) {
                const obj = objectMap.get(objData.uuid);
                if (objData.parentUuid) {
                    const parent = objectMap.get(objData.parentUuid);
                    if (parent) parent.add(obj);
                } else {
                    scene.add(obj);
                }
            }
            updateTreeView();
            isRestoringState = false; 
        }

        function undo() { if (historyStack.length <= 1) return; const currentState = historyStack.pop(); redoStack.push(currentState); const prevState = historyStack[historyStack.length - 1]; restoreState(prevState); updateHistoryButtons(); }
        function redo() { if (redoStack.length === 0) return; const nextState = redoStack.pop(); historyStack.push(nextState); restoreState(nextState); updateHistoryButtons(); }
        function updateHistoryButtons() { ui.undoBtn.disabled = historyStack.length <= 1; ui.redoBtn.disabled = redoStack.length === 0; }

        function setupEventListeners() {
            ui.toggleToolboxBtn.addEventListener('click', () => ui.leftPanelContainer.classList.toggle('visible'));
            ui.undoBtn.addEventListener('click', undo); ui.redoBtn.addEventListener('click', redo);
            ui.btnInteriorMode.addEventListener('click', () => { if (isInteriorMode) { exitInteriorMode(); } else if (primarySelection) { enterInteriorMode(primarySelection); } });
            document.getElementById('btn-add-box').addEventListener('click', () => addPrimitive('box')); document.getElementById('btn-add-sphere').addEventListener('click', () => addPrimitive('sphere')); document.getElementById('btn-add-cylinder').addEventListener('click', () => addPrimitive('cylinder'));
            document.getElementById('btn-export-glb').addEventListener('click', exportGLB);

            // SAVE TO SERVER BTN
            document.getElementById('btn-save-server').addEventListener('click', saveToServer);

            ui.btnLoadGlb.addEventListener('click', () => ui.glbFileInput.click());
            ui.glbFileInput.addEventListener('change', (event) => loadGLB(event));
            document.querySelectorAll('.panel-header').forEach(header => { header.addEventListener('click', () => { header.parentElement.classList.toggle('collapsed'); }); });

            const setMode = (mode, btn) => { 
                document.querySelectorAll('#toolbox-panel .button-group .btn').forEach(b => b.classList.remove('active')); 
                if(btn) btn.classList.add('active'); 

                if (mode === 'select') {
                    gizmoEnabled = false;
                    transformControls.detach();
                } else {
                    gizmoEnabled = true;
                    transformControls.setMode(mode);
                    if (primarySelection && !transformControls.object) {
                        if (!isInteriorMode || primarySelection !== interiorModeObject) {
                             transformControls.attach(primarySelection);
                        }
                    }
                }
            };

            document.getElementById('btn-select').addEventListener('click', (e) => setMode('select', e.currentTarget));
            document.getElementById('btn-translate').addEventListener('click', (e) => setMode('translate', e.currentTarget)); 
            document.getElementById('btn-rotate').addEventListener('click', (e) => setMode('rotate', e.currentTarget)); 
            document.getElementById('btn-scale').addEventListener('click', (e) => setMode('scale', e.currentTarget));

            document.getElementById('btn-union').addEventListener('click', () => applyBooleanOperation('union')); document.getElementById('btn-subtract').addEventListener('click', () => applyBooleanOperation('subtract')); document.getElementById('btn-intersect').addEventListener('click', () => applyBooleanOperation('intersect'));
            const alignModes = ['min-outer', 'min-inner', 'center', 'max-inner', 'max-outer'];
            ['x', 'y', 'z'].forEach(axis => { alignModes.forEach(mode => { document.getElementById(`align-${axis}-${mode}`).addEventListener('click', () => {alignObjects(axis, mode); saveState();}); }); });
            ui.gridSizeInput.addEventListener('change', () => { 
                const newSize = parseFloat(ui.gridSizeInput.value); 
                if (newSize > 0) { 
                    gridSize = newSize; 
                    transformControls.setTranslationSnap(gridSize); 
                    if (isInteriorMode) updateFloorplan(); else updateGrid();
                } 
            });
            ui.treeList.addEventListener('click', (event) => { const item = event.target.closest('li > .tree-item'); if (!item) return; const object = editableObjects.find(obj => obj.uuid === item.parentElement.dataset.uuid); if (event.target.closest('.btn-rename')) { if (object) startRename(object, item.querySelector('.tree-item-label')); } else if (event.target.closest('.btn-lock')) { if (object) { toggleLock(object); saveState(); } } else if (event.target.closest('.btn-delete-item')) { if (object) { deleteObject(object); } } else { if (object && !object.userData.isLocked) { selectObject(object, isShiftDown ? primarySelection : null); } } });
            const setupDimensionInput = (inputElement, axis) => { inputElement.addEventListener('change', (event) => { if (!primarySelection) return; const newSize = parseFloat(event.target.value); if (isNaN(newSize) || newSize <= 0) { updatePropertiesPanel(); return; } const geoSize = primarySelection.userData.geometrySize; const newScale = newSize / geoSize[axis]; primarySelection.scale[axis] = newScale; enforceGroundConstraint(primarySelection); updatePropertiesPanel(); saveState(); }); };
            setupDimensionInput(ui.propSizeX, 'x'); setupDimensionInput(ui.propSizeY, 'y'); setupDimensionInput(ui.propSizeZ, 'z');
            transformControls.addEventListener('mouseDown', onTransformStart);
            transformControls.addEventListener('objectChange', () => { onTransform(); enforceGroundConstraint(); updatePropertiesPanel(); if (isInteriorMode) updateFloorplan(); });
            transformControls.addEventListener('mouseUp', onTransformEnd);
            transformControls.addEventListener('dragging-changed', e => { orbitControls.enabled = !e.value; });
            window.addEventListener('keydown', e => { if (e.key === 'Shift') isShiftDown = true; }); window.addEventListener('keyup', e => { if (e.key === 'Shift') isShiftDown = false; });
            renderer.domElement.addEventListener('pointerdown', onPointerDown, false);
            window.addEventListener('keydown', onKeyDown);
            window.addEventListener('resize', onWindowResize);
        }

        // --- COMMON SCENE LOADING LOGIC ---
        function processLoadedScene(gltf, filename) {
            ui.loadedFileInfo.textContent = `Geladen: ${filename}`;
            let nodesToProcess = [...gltf.scene.children];
            let didUnwrap = false;

            for (let i=0; i<2; i++) {
                if (nodesToProcess.length === 1) {
                    const root = nodesToProcess[0];
                    if (!root.isMesh && root.children.length > 0) {
                        console.log("Löse Container auf:", root.name);
                        scene.add(root);
                        root.updateMatrixWorld(true);
                        const children = [...root.children];
                        nodesToProcess = []; 
                        children.forEach(child => {
                            scene.attach(child); 
                            nodesToProcess.push(child);
                        });
                        scene.remove(root); 
                        didUnwrap = true;
                    }
                }
            }

            let loadedCount = 0;
            nodesToProcess.forEach(node => {
                scene.add(node); 
                if (!didUnwrap && node.position.lengthSq() < 0.001) {
                    const distance = 15;
                    const pos = new THREE.Vector3(0, 0, -distance).applyMatrix4(camera.matrixWorld);
                    node.position.set(pos.x, 0, pos.z); 
                }
                node.traverse((n) => {
                    if (!n.name) n.name = `Node_${Math.random().toString(36).substr(2, 5)}`;
                    if (!editableObjects.includes(n)) {
                        if (n.isMesh) {
                            n.material = defaultMaterial.clone();
                            n.userData.originalMaterial = n.material.clone();
                            n.userData.isLocked = false;
                            if(!n.geometry.boundingBox) n.geometry.computeBoundingBox();
                            n.userData.geometrySize = n.geometry.boundingBox.getSize(new THREE.Vector3());
                            editableObjects.push(n);
                            loadedCount++;
                        } else if (n.type === 'Group' || n.type === 'Object3D') {
                            n.userData = n.userData || {};
                            n.userData.isLocked = false;
                            editableObjects.push(n);
                        }
                    }
                });
                if(node.isMesh) enforceGroundConstraint(node);
            });

            if (loadedCount > 0 || nodesToProcess.length > 0) {
                showInfoText(`Objekte geladen.`, 4000);
                updateTreeView(); 
                saveState();
            } else { showInfoText("Keine Objekte gefunden.", 5000); }
        }

        function loadGLB(event) {
            const file = event.target.files[0]; if (!file) return;
            const reader = new FileReader();
            reader.onload = (e) => {
                const contents = e.target.result; const loader = new GLTFLoader();
                loader.parse(contents, '', (gltf) => processLoadedScene(gltf, file.name), (error) => { console.error('Fehler:', error); showInfoText("Fehler beim Laden.", 5000); });
            };
            reader.readAsArrayBuffer(file); ui.glbFileInput.value = '';
        }

        // --- EXPORT & SAVE LOGIC ---
        function exportGLB() { 
            performExport(false);
        }

        function saveToServer() {
            if(!confirm("Möchtest du das Live-Modell auf dem Server überschreiben? Ein Backup der alten Version wird erstellt.")) return;
            showInfoText("Speichere auf Server...", 5000);
            performExport(true);
        }

        function performExport(uploadToServer) {
            if (editableObjects.length === 0) { alert("Die Szene ist leer."); return; } 
            const exportCandidates = editableObjects.filter(obj => obj.parent === scene);
            if(exportCandidates.length === 0) { alert("Keine exportierbaren Objekte."); return; }

            // Reset materials for clean export
            editableObjects.forEach(obj => { if(obj.userData.originalMaterial) obj.material = obj.userData.originalMaterial }); 

            new GLTFExporter().parse(exportCandidates, result => { 
                const blob = new Blob([result], { type: 'application/octet-stream' }); 

                if (uploadToServer) {
                    const formData = new FormData();
                    formData.append('file', blob);

                    fetch('/api/facility/save_model', {
                        method: 'POST',
                        body: formData
                    })
                    .then(response => response.json())
                    .then(data => {
                        if(data.status === 'success') {
                            if(confirm("Speichern erfolgreich! Zurück zum Viewer?")) {
                                window.location.href = "{{ url_for('serve_facility_viewer') }}";
                            } else {
                                showInfoText("Live-Modell aktualisiert.", 3000);
                            }
                        } else {
                            alert("Fehler beim Speichern: " + data.message);
                        }
                    })
                    .catch(err => {
                        console.error(err);
                        alert("Netzwerkfehler beim Speichern.");
                    })
                    .finally(() => {
                         // Restore highlight if selected
                        if (primarySelection) primarySelection.material = primaryHighlightMaterial; 
                        if (targetSelection) targetSelection.material = targetHighlightMaterial; 
                    });
                } else {
                    // Download
                    let fileName = ui.exportFilename.value.trim();
                    if (!fileName) fileName = 'layout';
                    if (!fileName.toLowerCase().endsWith('.glb')) fileName += '.glb';

                    const link = document.createElement('a'); 
                    link.href = URL.createObjectURL(blob); 
                    link.download = fileName; 
                    link.click(); 
                    URL.revokeObjectURL(link.href); 

                    if (primarySelection) primarySelection.material = primaryHighlightMaterial; 
                    if (targetSelection) targetSelection.material = targetHighlightMaterial; 
                }
            }, error => console.error('Export fehlgeschlagen:', error), { binary: true }); 
        }

        function enforceGroundConstraint(object) { 
            const obj = object || transformControls.object; 
            if (!obj) return; 

            if (obj.parent && obj.parent !== scene) {
                if (!obj.parent.geometry.boundingBox) obj.parent.geometry.computeBoundingBox();
                if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();

                const parentFloorY = obj.parent.geometry.boundingBox.min.y;
                const childHalfHeight = (obj.geometry.boundingBox.max.y - obj.geometry.boundingBox.min.y) * obj.scale.y * 0.5;
                const childBottomY = obj.position.y - childHalfHeight;

                if (childBottomY < parentFloorY) {
                    obj.position.y = parentFloorY + childHalfHeight;
                }
            } 
            else {
                obj.updateWorldMatrix(true, false);
                const boundingBox = new THREE.Box3().setFromObject(obj);
                const groundLevel = 0;
                if (boundingBox.min.y < groundLevel) {
                    obj.position.y += groundLevel - boundingBox.min.y;
                }
            }
        }

        function onTransformEnd(event) { isScaling = false; preTransformData = {}; saveState(); }

        function addPrimitive(type) {
            let name = type.charAt(0).toUpperCase() + type.slice(1);
            let sizeScale = isInteriorMode ? 0.2 : 1;

            const mesh = new THREE.Mesh( 
                type === 'sphere' ? new THREE.SphereGeometry(1 * sizeScale, 32, 16) : 
                type === 'cylinder' ? new THREE.CylinderGeometry(1 * sizeScale, 1 * sizeScale, 2 * sizeScale, 32) : 
                new THREE.BoxGeometry(2 * sizeScale, 2 * sizeScale, 2 * sizeScale), 
                defaultMaterial.clone() 
            );
            mesh.name = `${name}_${editableObjects.length + 1}`;

            mesh.userData.originalMaterial = mesh.material; mesh.userData.isLocked = false;
            mesh.geometry.computeBoundingBox();
            mesh.userData.geometrySize = mesh.geometry.boundingBox.getSize(new THREE.Vector3());
            editableObjects.push(mesh); 

            if (isInteriorMode && interiorModeObject) {
                interiorModeObject.add(mesh);

                if (!interiorModeObject.geometry.boundingBox) interiorModeObject.geometry.computeBoundingBox();
                const parentFloorY = interiorModeObject.geometry.boundingBox.min.y;
                const childHalfHeight = mesh.userData.geometrySize.y * mesh.scale.y * 0.5;

                mesh.position.set(0, parentFloorY + childHalfHeight, 0);
            } else {
                scene.add(mesh); 
                mesh.position.set(0, 0, 0);
            }

            selectObject(mesh, null); enforceGroundConstraint(mesh); updateTreeView(); saveState();
        }

        function removeObjectAndChildren(object) {
            while (object.children.length > 0) {
                const child = object.children[0];
                if (child === interiorGridHelper || child === floorplanMesh) {
                    object.remove(child); 
                    continue;
                }
                removeObjectAndChildren(child);
            }
            object.geometry.dispose();
            if (Array.isArray(object.material)) { object.material.forEach(m => m.dispose()); } else { object.material.dispose(); }
            if(object.userData.originalMaterial) object.userData.originalMaterial.dispose();

            const index = editableObjects.findIndex(obj => obj.uuid === object.uuid);
            if (index > -1) editableObjects.splice(index, 1);

            if (object.parent) object.parent.remove(object);
        }
        function deleteObject(objectToDelete, doSaveState = true) {
            if (!objectToDelete) return; if (isInteriorMode && objectToDelete === interiorModeObject) exitInteriorMode();
            if (transformControls.object && (transformControls.object === objectToDelete || transformControls.object.parent === objectToDelete)) transformControls.detach();
            if (primarySelection === objectToDelete) primarySelection = null; if (targetSelection === objectToDelete) targetSelection = null;

            removeObjectAndChildren(objectToDelete);

            if (doSaveState) saveState();
            updateTreeView(); updateSelectionUI();
        }

        function alignObjects(axis, mode) {
            if (!primarySelection || !targetSelection) return;

            let primaryBox, targetBox;
            const isLocalMode = primarySelection.parent && primarySelection.parent !== scene;

            if (isLocalMode) {
                primarySelection.updateMatrix();
                targetSelection.updateMatrix();

                if (!primarySelection.geometry.boundingBox) primarySelection.geometry.computeBoundingBox();
                if (!targetSelection.geometry.boundingBox) targetSelection.geometry.computeBoundingBox();

                primaryBox = primarySelection.geometry.boundingBox.clone();
                primaryBox.applyMatrix4(primarySelection.matrix);

                targetBox = targetSelection.geometry.boundingBox.clone();
                targetBox.applyMatrix4(targetSelection.matrix);
            } else {
                primaryBox = new THREE.Box3().setFromObject(primarySelection);
                targetBox = new THREE.Box3().setFromObject(targetSelection);
            }

            const primarySize = primaryBox.getSize(new THREE.Vector3()); 
            const primaryCenter = primaryBox.getCenter(new THREE.Vector3());
            const targetCenter = targetBox.getCenter(new THREE.Vector3()); 
            const positionOffset = primaryCenter.clone().sub(primarySelection.position);

            let targetPrimaryCenterValue;
            switch (mode) {
                case 'center': targetPrimaryCenterValue = targetCenter[axis]; break;
                case 'min-inner': targetPrimaryCenterValue = targetBox.min[axis] + primarySize[axis] / 2; break;
                case 'max-inner': targetPrimaryCenterValue = targetBox.max[axis] - primarySize[axis] / 2; break;
                case 'min-outer': targetPrimaryCenterValue = targetBox.min[axis] - primarySize[axis] / 2; break;
                case 'max-outer': targetPrimaryCenterValue = targetBox.max[axis] + primarySize[axis] / 2; break;
            }

            if (targetPrimaryCenterValue === undefined) return;
            primarySelection.position[axis] = targetPrimaryCenterValue - positionOffset[axis];
            enforceGroundConstraint(primarySelection);
        }

        function enterInteriorMode(object) {
            if (!object) return; isInteriorMode = true; interiorModeObject = object;
            const descendants = [];
            object.traverse(child => descendants.push(child));

            editableObjects.forEach(obj => { obj.visible = descendants.includes(obj); });

            transformControls.setSpace('local'); 

            originalGridSize = parseFloat(ui.gridSizeInput.value) || 1;
            gridSize = parseFloat((originalGridSize / 10).toPrecision(4));
            ui.gridSizeInput.value = gridSize; 
            transformControls.setTranslationSnap(gridSize);

            gridHelper.visible = false; 
            const floorplanGeo = new THREE.PlaneGeometry(1, 1); const floorplanMat = new THREE.MeshBasicMaterial({ color: 0xffa500, transparent: true, opacity: 0.5, side: THREE.DoubleSide });
            floorplanMesh = new THREE.Mesh(floorplanGeo, floorplanMat); floorplanMesh.rotation.x = -Math.PI / 2; 

            interiorModeObject.add(floorplanMesh); 

            updateFloorplan();
            const box = new THREE.Box3().setFromObject(object); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z);
            orbitControls.target.copy(center); targetPosition = new THREE.Vector3(center.x, center.y + maxDim * 0.75, center.z + maxDim * 1.5); targetLookAt = center.clone(); isAnimatingCamera = true;
            showInfoText("Innenraum-Modus: Gitter ist 10x feiner.", 4000);
            selectObject(interiorModeObject, null);

            if(ui.btnSaveServer) {
                ui.btnSaveServer.disabled = true;
                ui.btnSaveServer.title = "Speichern im Innenraum-Modus nicht möglich";
                ui.btnSaveServer.classList.add('opacity-50', 'cursor-not-allowed');
            }
        }

        function exitInteriorMode() {
            if (!isInteriorMode) return;

            if (floorplanMesh) { floorplanMesh.parent.remove(floorplanMesh); floorplanMesh.geometry.dispose(); floorplanMesh.material.dispose(); floorplanMesh = null; }
            if (interiorGridHelper) { interiorGridHelper.parent.remove(interiorGridHelper); interiorGridHelper.geometry.dispose(); interiorGridHelper.material.dispose(); interiorGridHelper = null; }

            editableObjects.forEach(obj => { obj.visible = true; });

            transformControls.setSpace('world');

            gridSize = originalGridSize; 
            ui.gridSizeInput.value = gridSize; 
            transformControls.setTranslationSnap(gridSize);

            gridHelper.visible = true;

            isInteriorMode = false; 
            interiorModeObject = null;

            selectObject(null, null); 
            transformControls.detach();

            showInfoText("Innenraum-Modus deaktiviert.", 3000);
            if(ui.btnSaveServer) {
                ui.btnSaveServer.disabled = false;
                ui.btnSaveServer.title = "";
                ui.btnSaveServer.classList.remove('opacity-50', 'cursor-not-allowed');
            }
        }

        function updateFloorplan() {
            if (!isInteriorMode || !interiorModeObject) return;
            if (!interiorModeObject.geometry.boundingBox) interiorModeObject.geometry.computeBoundingBox();
            const bbox = interiorModeObject.geometry.boundingBox;
            const sizeX = bbox.max.x - bbox.min.x;
            const sizeZ = bbox.max.z - bbox.min.z;
            const localY = bbox.min.y;

            if(floorplanMesh) { 
                floorplanMesh.scale.set(sizeX, sizeZ, 1); 
                floorplanMesh.position.set(0, localY + 0.01, 0); 
            }
            if (interiorGridHelper) { 
                interiorGridHelper.parent.remove(interiorGridHelper); 
                interiorGridHelper.geometry.dispose(); 
                interiorGridHelper.material.dispose(); 
            }

            const interiorGridSize = Math.max(sizeX, sizeZ); 
            const divisions = Math.max(2, Math.round(interiorGridSize / gridSize));
            interiorGridHelper = new THREE.GridHelper(interiorGridSize, divisions, 0xffffff, 0xaaaaaa); 

            interiorGridHelper.position.set(0, localY + 0.02, 0);
            interiorModeObject.add(interiorGridHelper); 
        }

        function applyBooleanOperation(operation) { if (isInteriorMode) exitInteriorMode(); if (!primarySelection || !targetSelection) { showInfoText("Bitte wählen Sie genau zwei Objekte aus."); return; } primarySelection.updateMatrix(); targetSelection.updateMatrix(); let resultMesh; try { let csgResult; switch (operation) { case 'union': csgResult = CSG.union(primarySelection, targetSelection); break; case 'subtract': csgResult = CSG.subtract(primarySelection, targetSelection); break; case 'intersect': csgResult = CSG.intersect(primarySelection, targetSelection); break; default: throw new Error("Unbekannte Operation"); } resultMesh = csgResult; resultMesh.material = defaultMaterial.clone(); } catch (e) { console.error("Boolesche Operation fehlgeschlagen:", e); showInfoText("Operation fehlgeschlagen.", 5000); return; } const name = `${operation.charAt(0).toUpperCase() + operation.slice(1)}_${editableObjects.length + 1}`; resultMesh.name = name; resultMesh.userData.originalMaterial = resultMesh.material; resultMesh.userData.isLocked = false; resultMesh.geometry.computeBoundingBox(); resultMesh.userData.geometrySize = resultMesh.geometry.boundingBox.getSize(new THREE.Vector3()); const oldP = primarySelection; const oldT = targetSelection; deleteObject(oldP, false); deleteObject(oldT, false); scene.add(resultMesh); editableObjects.push(resultMesh); selectObject(resultMesh, null); enforceGroundConstraint(resultMesh); updateTreeView(); saveState(); }

        function updatePropertiesPanel() { 
            if (primarySelection) { 
                ui.propertiesPanel.style.display = 'block'; 
                const size = primarySelection.userData.geometrySize.clone().multiply(primarySelection.scale); 
                ui.propSizeX.value = size.x.toFixed(3); 
                ui.propSizeY.value = size.y.toFixed(3); 
                ui.propSizeZ.value = size.z.toFixed(3); 

                const interiorModeGroup = ui.btnInteriorMode.parentElement; 
                if (interiorModeGroup) { 
                    interiorModeGroup.style.display = (primarySelection && !targetSelection) ? 'block' : 'none'; 
                    if (isInteriorMode) {
                        ui.btnInteriorMode.innerHTML = `<i data-lucide="scan-off"></i>Modus verlassen`;
                    } else {
                        ui.btnInteriorMode.innerHTML = `<i data-lucide="scan"></i>Innenraum-Modus`;
                    }
                    lucide.createIcons({ nodes: [ui.btnInteriorMode] });
                } 
            } else { 
                ui.propertiesPanel.style.display = 'none'; 
            } 
        }

        function selectObject(primary, target) {
            if (isInteriorMode) {
                let current = primary; let isInGroup = false;
                while(current) { if(current === interiorModeObject) { isInGroup = true; break; } current = current.parent; }
                if (primary && !isInGroup) {
                    // Option: Selection outside group ignored
                }
            }

            if (primary && primary.userData.isLocked) return;

            if (primarySelection) primarySelection.material = primarySelection.userData.originalMaterial; 
            if (targetSelection) targetSelection.material = targetSelection.userData.originalMaterial; 

            primarySelection = primary; 
            targetSelection = target;

            if (primarySelection) { 
                // ### FIX: Check gizmoEnabled flag ###
                if (isInteriorMode && primarySelection === interiorModeObject) {
                    primarySelection.material = primaryHighlightMaterial; 
                    transformControls.detach(); 
                } else if (gizmoEnabled) {
                    primarySelection.material = primaryHighlightMaterial; 
                    transformControls.attach(primarySelection); 
                } else {
                    // Gizmo disabled (Select Mode)
                    primarySelection.material = primaryHighlightMaterial;
                    transformControls.detach();
                }
            } else { 
                transformControls.detach(); 
            }

            if (targetSelection) { targetSelection.material = targetHighlightMaterial; } 
            updateSelectionUI(); updateTreeView(); updatePropertiesPanel();

            if (ui.alignmentPanel.style.display === 'block') {
                requestAnimationFrame(() => lucide.createIcons({ nodes: [ui.alignmentPanel] }));
            }
        }

        function onTransformStart(event) { const object = transformControls.object; if (!object) return; if (transformControls.mode === 'scale') { isScaling = true; preTransformData = { position: object.position.clone(), scale: object.scale.clone() }; showInfoText("Halte SHIFT für symmetrische Skalierung"); } }
        function onTransform() { const object = transformControls.object; if (!object || !isScaling || isShiftDown || !object.userData.geometrySize) return; const geoSize = object.userData.geometrySize; const initialSize = geoSize.clone().multiply(preTransformData.scale); const currentSize = geoSize.clone().multiply(object.scale); const deltaSize = currentSize.clone().sub(initialSize); const positionOffset = deltaSize.clone().divideScalar(2); positionOffset.applyQuaternion(object.quaternion); object.position.copy(preTransformData.position).add(positionOffset); }
        function showInfoText(text, duration = 4000) { ui.infoText.textContent = text; ui.infoText.classList.add('visible'); setTimeout(() => ui.infoText.classList.remove('visible'), duration); }
        function updateGrid() { if (gridHelper) scene.remove(gridHelper); const totalGridSize = 200; const divisions = totalGridSize / gridSize; gridHelper = new THREE.GridHelper(totalGridSize, divisions, 0xaaaaaa, 0xdddddd); scene.add(gridHelper); }
        function initAxisHelper() { axisScene = new THREE.Scene(); axisCamera = new THREE.PerspectiveCamera(50, 1, 0.1, 1000); axisCamera.up = camera.up; const axes = new THREE.AxesHelper(5); axes.material.linewidth = 3; axisScene.add(axes); const sphereGeo = new THREE.SphereGeometry(0.5, 16, 8); const makeAxisEnd = (name, position, color) => { const mesh = new THREE.Mesh(sphereGeo, new THREE.MeshBasicMaterial({ color, toneMapped: false })); mesh.name = name; mesh.position.copy(position); axisScene.add(mesh); axisElements.push(mesh); }; makeAxisEnd("axis_x", new THREE.Vector3(5, 0, 0), 0xff0000); makeAxisEnd("axis_y", new THREE.Vector3(0, 5, 0), 0x00ff00); makeAxisEnd("axis_z", new THREE.Vector3(0, 0, 5), 0x0000ff); }

        function updateSelectionUI() { 
            const hasTwoSelections = primarySelection && targetSelection; 
            ui.alignmentPanel.style.display = hasTwoSelections ? 'block' : 'none'; 
            ui.booleanOperationsPanel.style.display = hasTwoSelections ? 'block' : 'none'; 
            if (hasTwoSelections) { 
                document.getElementById('align-primary-name').textContent = primarySelection.name; 
                document.getElementById('align-target-name').textContent = targetSelection.name; 
                requestAnimationFrame(() => lucide.createIcons({ nodes: [ui.alignmentPanel] }));
            } 
        }

        function toggleLock(object) { object.userData.isLocked = !object.userData.isLocked; if (object.userData.isLocked && primarySelection === object) { selectObject(null, null); } updateTreeView(); }
        function startRename(object, labelElement) { labelElement.innerHTML = `<input type="text" class="nodename-input-inline" value="${object.name}">`; const input = labelElement.querySelector('input'); input.focus(); input.select(); const finishEdit = () => { const newName = input.value.trim(); if (newName) object.name = newName; input.removeEventListener('blur', finishEdit); updateTreeView(); saveState(); }; input.addEventListener('blur', finishEdit); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') finishEdit(); if(e.key === 'Escape') { input.removeEventListener('blur', finishEdit); updateTreeView(); } }); }
        function duplicateSelected() {
            if (!primarySelection) return;
            const original = primarySelection;
            const clone = original.clone(true); 
            clone.traverse((node) => {
                if (node.isMesh) {
                    node.name = node.name + "_Kopie";
                    node.material = defaultMaterial.clone();
                    node.userData = JSON.parse(JSON.stringify(node.userData));
                    node.userData.originalMaterial = node.material;
                    node.userData.isLocked = false;
                    node.uuid = THREE.MathUtils.generateUUID(); 
                    if (!node.geometry.boundingBox) node.geometry.computeBoundingBox();
                    editableObjects.push(node);
                }
            });
            clone.position.x += 2; clone.position.z += 2;
            if (original.parent) { original.parent.add(clone); } else { scene.add(clone); }
            enforceGroundConstraint(clone); selectObject(clone, null); updateTreeView(); saveState(); showInfoText("Objekt dupliziert", 2000);
        }

        function focusCameraOnSelection() {
            if (!primarySelection) return;
            const box = new THREE.Box3().setFromObject(primarySelection);
            if (box.isEmpty()) return;
            const center = box.getCenter(new THREE.Vector3());
            const size = box.getSize(new THREE.Vector3());
            const maxDim = Math.max(size.x, size.y, size.z);
            targetLookAt = center.clone();
            const direction = camera.position.clone().sub(orbitControls.target).normalize();
            const distance = Math.max(maxDim * 2.5, 5); 
            targetPosition = center.clone().add(direction.multiplyScalar(distance));
            isAnimatingCamera = true; orbitControls.enabled = false;
        }

        function onKeyDown(event) { 
            if(event.ctrlKey && event.key.toLowerCase() === 'z') { event.preventDefault(); undo(); return; } 
            if((event.ctrlKey && event.key.toLowerCase() === 'y') || (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'z')) { event.preventDefault(); redo(); return; } 
            if (document.activeElement.tagName === 'INPUT') return; 
            switch (event.key.toLowerCase()) { 
                case 'q': document.getElementById('btn-select').click(); break; 
                case 'w': document.getElementById('btn-translate').click(); break; 
                case 'e': document.getElementById('btn-rotate').click(); break; 
                case 'r': document.getElementById('btn-scale').click(); break; 
                case 'delete': case 'backspace': if(primarySelection) { deleteObject(primarySelection); } break; 
                case 'escape': selectObject(null, null); break; 
                case 'f': focusCameraOnSelection(); break;
                case 'd': if (event.ctrlKey) { event.preventDefault(); duplicateSelected(); } break;
            } 
        }
        function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
        function animateCameraToView(axis) { const distance = camera.position.distanceTo(orbitControls.target); targetLookAt = new THREE.Vector3(0, 0, 0); if (axis === 'x') targetPosition = new THREE.Vector3(distance, 0, 0); else if (axis === 'y') targetPosition = new THREE.Vector3(0, distance, 0.01); else if (axis === 'z') targetPosition = new THREE.Vector3(0, 0, distance); isAnimatingCamera = true; orbitControls.enabled = false; }

        function buildTreeItemHTML(object) {
            const isLocked = object.userData.isLocked; const isActive = primarySelection && object.uuid === primarySelection.uuid;
            const lockIcon = isLocked ? 'lock' : 'unlock'; const lockTitle = isLocked ? 'Entsperren' : 'Sperren';
            const hierarchyIcon = object.parent !== scene ? `<span class="tree-item-icon"><i data-lucide="corner-down-right"></i></span>` : '';

            let html = `<li data-uuid="${object.uuid}" class="${isLocked ? 'locked' : ''}"><div class="tree-item ${isActive ? 'active' : ''}"><span class="tree-item-label">${hierarchyIcon}${object.name}</span><div class="tree-item-actions"><button class="btn-icon btn-rename" title="Umbenennen"><i data-lucide="pencil"></i></button><button class="btn-icon btn-lock ${isLocked ? 'locked' : ''}" title="${lockTitle}"><i data-lucide="${lockIcon}"></i></button><button class="btn-icon btn-delete-item" title="Löschen"><i data-lucide="trash-2"></i></button></div></div>`;

            const children = object.children.filter(child => editableObjects.includes(child));
            if (children.length > 0) {
                html += '<ul>';
                children.forEach(child => { html += buildTreeItemHTML(child); });
                html += '</ul>';
            }
            html += '</li>'; return html;
        }

        function updateTreeView() {
            const topLevelObjects = editableObjects.filter(obj => obj.parent === scene);
            ui.treeList.innerHTML = topLevelObjects.map(obj => buildTreeItemHTML(obj)).join('');
            lucide.createIcons({ nodes: [ui.treeList] });
        }

        function onPointerDown(event) { 
            if (isAnimatingCamera) return; 
            const size = 150; 
            const isClickOnAxisHelper = event.clientX > window.innerWidth - size && event.clientY < size; 
            if (isClickOnAxisHelper) { 
                pointer.x = ((event.clientX - (window.innerWidth - size)) / size) * 2 - 1; 
                pointer.y = -(event.clientY / size) * 2 + 1; 
                raycaster.setFromCamera(pointer, axisCamera); 
                const intersects = raycaster.intersectObjects(axisElements, false); 
                if (intersects.length > 0) { const axis = intersects[0].object.name.split('_')[1]; animateCameraToView(axis); } 
            } else { 
                if (transformControls.dragging) return; 
                pointer.x = (event.clientX / window.innerWidth) * 2 - 1; 
                pointer.y = - (event.clientY / window.innerHeight) * 2 + 1; 
                raycaster.setFromCamera(pointer, camera); 

                const intersects = raycaster.intersectObjects(editableObjects, false); 

                const object = intersects.length > 0 ? intersects[0].object : null; 
                if (object && object.userData.isLocked) return; 
                if (isShiftDown) { 
                    if (object && object !== primarySelection) selectObject(primarySelection, object); 
                    else selectObject(primarySelection, null); 
                } else { 
                    selectObject(object, null); 
                } 
            } 
        }
        function animate() { requestAnimationFrame(animate); if (isAnimatingCamera && targetPosition && targetLookAt) { camera.position.lerp(targetPosition, 0.1); orbitControls.target.lerp(targetLookAt, 0.1); if (camera.position.distanceTo(targetPosition) < 0.1) { camera.position.copy(targetPosition); orbitControls.target.copy(targetLookAt); targetPosition = null; targetLookAt = null; isAnimatingCamera = false; orbitControls.enabled = true; } } orbitControls.update(); renderer.autoClear = true; renderer.setViewport(0, 0, window.innerWidth, window.innerHeight); renderer.render(scene, camera); renderer.autoClear = false; renderer.clearDepth(); const size = 150; axisCamera.position.copy(camera.position).sub(orbitControls.target).setLength(15); axisCamera.lookAt(axisScene.position); renderer.setViewport(window.innerWidth - size, 0, size, size); renderer.render(axisScene, axisCamera); }

        init();
    </script>
</body>
</html>
"""
TRAINING_PAGE_HTML = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="utf-8"/>
    <title>MBET - Training Center</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        #sidebar { transition: width 0.3s ease-in-out; }
        #sidebar.is-collapsed { width: 5rem; }
        #sidebar.is-collapsed .sidebar-label, #sidebar.is-collapsed .sidebar-title, #sidebar.is-collapsed .user-info { display: none; }
        #sidebar.is-collapsed .sidebar-link, #sidebar.is-collapsed .sidebar-header, #sidebar.is-collapsed .user-profile { justify-content: center; }
        #sidebar.is-collapsed .sidebar-link .material-icons-outlined, #sidebar.is-collapsed .sidebar-header .material-icons-outlined { margin-right: 0; }
        .sidebar-label { transition: opacity 0.2s ease-in-out; }
        .sidebar-link.active { background-color: #e5e7eb; color: #0c4a6e; font-weight: 600; }
        .sidebar-link.active .material-icons-outlined { color: #0c4a6e; }
        .sidebar-separator { display: none; }
        #sidebar.is-collapsed .sidebar-separator { display: block; height: 1px; background-color: #e2e8f0; margin: 0.5rem 0.75rem; }
        .sidebar-link:hover { background-color: #f3f4f6; color: #0c4a6e; }
        .sidebar-link:hover .material-icons-outlined { color: #0c4a6e; }
    </style>
</head>
<body class="bg-white">
<div class="flex h-screen bg-white">

    {% include 'sidebar.html' %}

    <main class="flex-1 overflow-y-auto bg-neutral-50">
        <header class="flex items-center sticky top-0 z-50 justify-between whitespace-nowrap px-6 py-4 bg-white border-b border-slate-200">
             <h1 class="text-2xl font-bold tracking-tight text-slate-800">Training Center</h1>
        </header>

        <div class="p-4 sm:p-6 lg:p-8">

            <!-- Klassische Tab-Navigation -->
            <div class="mb-6">
                <nav class="flex space-x-6 border-b border-slate-200 overflow-x-auto" aria-label="Tabs">
                    <button onclick="switchView('dashboard', this)" id="tab-dashboard" class="px-1 pb-3 text-sm font-medium text-blue-600 border-b-2 border-blue-600 whitespace-nowrap">Terminübersicht</button>
                    {% if 'ADMIN' in session.get('roles', []) or 'PLANNER' in session.get('roles', []) or 'TESTCELLINSPECTOR' in session.get('roles', []) or 'TESTENGINEER' in session.get('roles', []) or 'MEASUREMENTENGINEER' in session.get('roles', [])  %}
                        <button onclick="switchView('catalog', this)" id="tab-catalog" class="px-1 pb-3 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent hover:border-slate-300 whitespace-nowrap">Trainings-Katalog</button>
                    {% endif %}    
                </nav>
            </div>

            <!-- VIEW 1: DASHBOARD (Termine) -->
            <div id="view-dashboard" class="space-y-6">
                <div class="flex justify-between items-center">
                    <h2 class="text-lg font-bold text-slate-800">Geplante Sessions</h2>
                    <button onclick="openSessionModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 flex items-center gap-2 shadow-sm">
                        <span class="material-icons-outlined text-base">event</span> Neue Session planen
                    </button>
                </div>
                <div id="sessions-list" class="space-y-4"></div>
            </div>

            <!-- VIEW 2: KATALOG -->
            <div id="view-catalog" class="hidden space-y-6">
                 <div class="flex justify-between items-center">
                    <h2 class="text-lg font-bold text-slate-800">Verfügbare Trainingsmodule</h2>
                    <button onclick="openCatalogModal()" class="bg-white border border-slate-300 text-slate-700 px-4 py-2 rounded-lg text-sm font-medium hover:bg-slate-50 flex items-center gap-2 shadow-sm">
                        <span class="material-icons-outlined text-base">add</span> Neues Modul
                    </button>
                </div>
                <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="catalog-grid"></div>
            </div>

        </div>
    </main>
</div>

<!-- CATALOG MODAL (Create & Edit) -->
<div id="catalogModal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-50 z-[60] flex items-center justify-center backdrop-blur-sm">
    <div class="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden">
        <div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
            <h3 class="font-bold text-lg text-slate-800" id="catModalTitle">Trainingsmodul</h3>
            <div class="flex gap-2">
                <button id="btnDeleteCatalog" onclick="deleteCatalogItem()" class="text-red-400 hover:text-red-600 hidden"><span class="material-icons-outlined">delete</span></button>
                <button onclick="document.getElementById('catalogModal').classList.add('hidden')" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button>
            </div>
        </div>
        <div class="p-6 space-y-4">
            <input type="hidden" id="catId">
            <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Titel</label><input id="catTitle" class="w-full border-slate-300 rounded-md text-sm"></div>
            <div class="grid grid-cols-2 gap-4">
                <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Dauer (Std)</label><input type="number" id="catDuration" class="w-full border-slate-300 rounded-md text-sm"></div>
                <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Kategorie</label><select id="catCategory" class="w-full border-slate-300 rounded-md text-sm bg-white"><option>Allgemein</option><option>Technik</option><option>Sicherheit</option><option>Qualität</option></select></div>
            </div>
            <div><label class="block text-xs font-bold text-slate-500 uppercase mb-1">Beschreibung</label><textarea id="catDesc" class="w-full border-slate-300 rounded-md text-sm" rows="3"></textarea></div>
        </div>
            <div>
                <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Titelbild (Optional)</label>
                <div class="flex items-center gap-3">
                    <input type="file" id="catImageInput" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" accept="image/*">
                    <input type="hidden" id="catImagePath">
                    <img id="catImagePreview" class="h-10 w-10 object-cover rounded hidden">
                </div>
            </div>
        <div class="px-6 py-4 bg-slate-50 border-t border-slate-100 flex justify-end gap-3">
            <button onclick="document.getElementById('catalogModal').classList.add('hidden')" class="px-4 py-2 text-slate-600 hover:bg-white rounded-lg text-sm font-medium border border-transparent hover:border-slate-200">Abbrechen</button>
            <button onclick="saveCatalogItem()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium shadow-sm">Speichern</button>
        </div>
    </div>
</div>

<!-- HISTORY MODAL -->
<div id="historyModal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-50 z-[70] flex items-center justify-center backdrop-blur-sm">
    <div class="bg-white rounded-xl shadow-2xl w-full max-w-2xl overflow-hidden max-h-[80vh] flex flex-col">
        <div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50 flex-shrink-0">
            <div>
                <h3 class="font-bold text-lg text-slate-800" id="histTitle">Verlauf</h3>
                <p class="text-xs text-slate-500 mt-1">Vergangene Sessions</p>
            </div>
            <button onclick="document.getElementById('historyModal').classList.add('hidden')" class="text-slate-400 hover:text-slate-600"><span class="material-icons-outlined">close</span></button>
        </div>
        <div class="p-6 overflow-y-auto custom-scrollbar flex-grow" id="historyList"></div>
    </div>
</div>

<!-- SESSION MODAL (View & Edit) -->
<div id="sessionModal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-50 z-[60] flex items-center justify-center backdrop-blur-sm">
    <div class="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90vh]">
        <!-- Header -->
        <div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50 flex-shrink-0">
            <h3 class="font-bold text-lg text-slate-800" id="sessModalTitle">Session Details</h3>
            <div class="flex gap-2 items-center">
                <button id="btnEditSession" onclick="switchToSessionEdit()" class="text-slate-400 hover:text-blue-600 p-1.5 rounded-full hover:bg-slate-100 transition-colors" title="Bearbeiten">
                    <span class="material-icons-outlined text-lg">edit</span>
                </button>
                <button onclick="document.getElementById('sessionModal').classList.add('hidden')" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-full hover:bg-slate-100 transition-colors">
                    <span class="material-icons-outlined text-lg">close</span>
                </button>
            </div>
        </div>

        <!-- Content: Scrollbar -->
        <div class="p-6 overflow-y-auto custom-scrollbar flex-grow">
            
            <!-- VIEW MODE -->
            <div id="viewSessionContent" class="space-y-6">
                <!-- Titel & Zeit -->
                <div>
                    <h2 id="viewSessTitle" class="text-xl font-bold text-slate-900 mb-1"></h2>
                    <div class="flex items-center gap-4 text-sm text-slate-600">
                        <span class="flex items-center gap-1"><span class="material-icons-outlined text-base text-slate-400">event</span> <span id="viewSessDate"></span></span>
                        <span class="flex items-center gap-1"><span class="material-icons-outlined text-base text-slate-400">schedule</span> <span id="viewSessTime"></span></span>
                    </div>
                </div>

                <!-- Info Grid -->
                <div class="grid grid-cols-2 gap-4 bg-slate-50 p-4 rounded-lg border border-slate-100 text-sm">
                    <div>
                        <p class="text-xs font-bold text-slate-400 uppercase">Ort</p>
                        <p id="viewSessLocation" class="font-medium text-slate-700"></p>
                    </div>
                    <div>
                        <p class="text-xs font-bold text-slate-400 uppercase">Trainer</p>
                        <p id="viewSessTrainer" class="font-medium text-slate-700"></p>
                    </div>
                </div>

                <!-- Beschreibung -->
                <div>
                    <p class="text-xs font-bold text-slate-400 uppercase mb-1">Inhalt</p>
                    <p id="viewSessDesc" class="text-sm text-slate-600 leading-relaxed whitespace-pre-wrap"></p>
                </div>

                <!-- Teilnehmer -->
                <div>
                    <div class="flex justify-between items-center mb-2">
                        <p class="text-xs font-bold text-slate-400 uppercase">Teilnehmer</p>
                        <span id="viewSessCount" class="text-xs bg-slate-100 px-2 py-0.5 rounded text-slate-500 font-mono"></span>
                    </div>
                    <div id="viewSessParticipants" class="flex flex-wrap gap-2"></div>
                </div>
            </div>

            <!-- EDIT MODE (Formular) -->
            <div id="editSessionContent" class="space-y-4 hidden">
                <input type="hidden" id="sessId">
                <div>
                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Training wählen</label>
                    <select id="sessCatalogId" class="w-full border-slate-300 rounded-md text-sm bg-white focus:ring-blue-500"></select>
                </div>
                <div class="grid grid-cols-2 gap-4">
                    <div>
                        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Datum</label>
                        <input type="date" id="sessDate" class="w-full border-slate-300 rounded-md text-sm">
                    </div>
                    <div>
                        <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Uhrzeit</label>
                        <input type="time" id="sessTime" class="w-full border-slate-300 rounded-md text-sm">
                    </div>
                </div>
                <div>
                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Ort</label>
                    <input id="sessLocation" class="w-full border-slate-300 rounded-md text-sm" placeholder="z.B. Schulungsraum 1">
                </div>
                <div>
                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Trainer</label>
                    <select id="sessTrainer" class="w-full border-slate-300 rounded-md text-sm bg-white"></select>
                </div>
                <div>
                    <label class="block text-xs font-bold text-slate-500 uppercase mb-1">Teilnehmer (Mehrfachauswahl)</label>
                    <input id="sessParticipantSearch" type="text" placeholder="Mitarbeiter suchen..." oninput="filterParticipants(this.value)" class="w-full border-slate-300 rounded-md text-sm mb-1" autocomplete="off">
                    <div id="sessParticipants" class="border border-slate-300 rounded-md max-h-32 overflow-y-auto p-2 bg-white space-y-1"></div>
                </div>
            </div>
        </div>

        <!-- Footer -->
        <div class="px-6 py-4 bg-slate-50 border-t border-slate-100 flex justify-between items-center flex-shrink-0" id="sessFooter">
            <!-- View Mode Footer -->
            <div id="viewSessFooter" class="w-full flex justify-end gap-3">
                <button id="btnJoinSession" onclick="toggleParticipation(true)" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 text-sm font-medium shadow-sm transition-colors hidden">Teilnehmen</button>
                <button id="btnLeaveSession" onclick="toggleParticipation(false)" class="px-4 py-2 bg-red-50 text-red-600 border border-red-200 rounded-lg hover:bg-red-100 text-sm font-medium transition-colors hidden">Absagen</button>
                <button onclick="document.getElementById('sessionModal').classList.add('hidden')" class="px-4 py-2 text-slate-600 hover:bg-white rounded-lg text-sm font-medium border border-transparent hover:border-slate-200">Schließen</button>
            </div>

            <!-- Edit Mode Footer -->
            <div id="editSessFooter" class="w-full flex justify-between gap-3 hidden">
                <button id="btnDeleteSession" onclick="deleteSession()" class="text-red-500 hover:text-red-700 font-medium text-sm flex items-center gap-1"><span class="material-icons-outlined text-base">delete</span> Löschen</button>
                <div class="flex gap-2">
                    <button onclick="switchToSessionView()" class="px-4 py-2 text-slate-600 hover:bg-white rounded-lg text-sm font-medium border border-transparent hover:border-slate-200">Abbrechen</button>
                    <button onclick="saveSession()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium shadow-sm">Speichern</button>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    let users = {{ users | tojson }};
    let trainingData = { catalog: [], sessions: [] };
    const currentUserId = "{{ session.get('user_id', '').lower() }}"; // Template Variable

    function filterParticipants(query) {
        const q = query.toLowerCase();
        document.querySelectorAll('#sessParticipants label').forEach(label => {
            label.style.display = label.textContent.toLowerCase().includes(q) ? '' : 'none';
        });
    }
    
    document.addEventListener('DOMContentLoaded', () => {
        const sidebar = document.getElementById('sidebar');
        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
        if(sidebar && sidebarToggleBtn) { 
            const toggleBtnIcon = sidebarToggleBtn.querySelector('.material-icons-outlined');
            const applyState = (isCollapsed) => {
                if (isCollapsed) { sidebar.classList.add('is-collapsed'); if(toggleBtnIcon) toggleBtnIcon.textContent = 'menu'; } 
                else { sidebar.classList.remove('is-collapsed'); if(toggleBtnIcon) toggleBtnIcon.textContent = 'chevron_left'; }
            };
            sidebarToggleBtn.addEventListener('click', () => { 
                const isNowCollapsed = !sidebar.classList.contains('is-collapsed');
                localStorage.setItem('sidebarCollapsed', isNowCollapsed); 
                applyState(isNowCollapsed);
            }); 
            if(localStorage.getItem('sidebarCollapsed') === 'true') { applyState(true); }
        }
        init();
    });

    async function init() {
        try {
            const trainRes = await fetch('/api/training/data');
            trainingData = await trainRes.json();
            if(!trainingData.catalog) trainingData.catalog = [];
            if(!trainingData.sessions) trainingData.sessions = [];
        } catch(e) {}
        renderCatalog(); renderSessions();
    }

    function switchView(viewName, btn) {
        // 1. Content umschalten
        ['dashboard', 'catalog'].forEach(v => {
            document.getElementById('view-' + v).classList.add('hidden');
            
            // Buttons zurücksetzen (Inaktiv-Style)
            const b = document.getElementById('tab-' + v);
            b.classList.remove('text-blue-600', 'border-blue-600');
            b.classList.add('text-slate-500', 'border-transparent', 'hover:text-slate-700', 'hover:border-slate-300');
        });
        
        // 2. Aktiven Content zeigen
        document.getElementById('view-' + viewName).classList.remove('hidden');
        
        // 3. Aktiven Button stylen
        // Falls btn nicht übergeben wurde (z.B. externer Aufruf), suchen wir ihn
        const activeBtn = btn || document.getElementById('tab-' + viewName);
        activeBtn.classList.remove('text-slate-500', 'border-transparent', 'hover:text-slate-700', 'hover:border-slate-300');
        activeBtn.classList.add('text-blue-600', 'border-blue-600');
    }

    // --- SESSIONS ---
    function renderSessions() {
        const list = document.getElementById('sessions-list');
        list.innerHTML = '';
        const today = new Date().toISOString().split('T')[0];

        // Nur Zukunft anzeigen (Vergangenheit im Archiv)
        const upcoming = trainingData.sessions.filter(s => s.date >= today);
        upcoming.sort((a,b) => new Date(a.date) - new Date(b.date));

        if (upcoming.length === 0) { list.innerHTML = '<div class="text-center py-12 bg-slate-50 rounded-xl border border-dashed border-slate-300 text-slate-400 italic">Keine anstehenden Termine.</div>'; return; }

        upcoming.forEach(sess => {
            const dateObj = new Date(sess.date);
            const month = dateObj.toLocaleString('de-DE', { month: 'short' }).toUpperCase();
            const day = dateObj.getDate();
            const catalogItem = trainingData.catalog.find(c => c.id == sess.catalogId) || { title: 'Unbekannt' };

            let avatars = '';
            sess.participants.slice(0, 5).forEach(pid => {
                avatars += `<div class="inline-flex h-6 w-6 items-center justify-center rounded-full ring-2 ring-white bg-slate-200 text-xs font-bold text-slate-600" title="${getUserName(pid)}">${getUserInitials(pid)}</div>`;
            });

            list.innerHTML += `
                <div class="flex items-start gap-4 p-4 bg-white rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer" onclick="openSessionModal('${sess.id}')">
                    <div class="flex-shrink-0 text-center w-12 bg-slate-50 rounded border border-slate-100 p-1">
                        <span class="block text-[10px] text-red-500 font-bold uppercase tracking-wider">${month}</span>
                        <span class="block text-xl font-bold text-slate-800 leading-none mt-1">${day}</span>
                    </div>
                    <div>
                        <h3 class="font-bold text-slate-800 text-sm">${catalogItem.title}</h3>
                        <p class="text-xs text-slate-500 mt-1 flex items-center gap-3">
                            <span><span class="material-icons-outlined text-[14px] align-text-bottom">schedule</span> ${sess.time}</span>
                            <span><span class="material-icons-outlined text-[14px] align-text-bottom">place</span> ${sess.location}</span>
                            <span><span class="material-icons-outlined text-[14px] align-text-bottom">person</span> ${getUserName(sess.trainerId)}</span>
                        </p>
                        <div class="mt-2 flex -space-x-2 overflow-hidden items-center">${avatars}</div>
                    </div>
                </div>`;
        });
    }

    function openSessionModal(sessId = null) {
        // Daten vorbereiten (Selects füllen) wie bisher...
        const catSelect = document.getElementById('sessCatalogId'); catSelect.innerHTML = '';
        trainingData.catalog.forEach(c => catSelect.innerHTML += `<option value="${c.id}">${c.title}</option>`);
        const trainerSelect = document.getElementById('sessTrainer'); trainerSelect.innerHTML = '';
        users.forEach(u => trainerSelect.innerHTML += `<option value="${u.id}">${u.nachname}, ${u.vorname}</option>`);

        if(sessId) {
            // EXISTING -> View Mode
            const sess = trainingData.sessions.find(s => s.id == sessId);
            if(sess) {
                // Populate View
                const catItem = trainingData.catalog.find(c => c.id == sess.catalogId) || {};
                document.getElementById('viewSessTitle').textContent = catItem.title || 'Unbekannt';
                document.getElementById('viewSessDesc').textContent = catItem.description || '';
                
                const d = new Date(sess.date);
                document.getElementById('viewSessDate').textContent = d.toLocaleDateString('de-DE', { weekday:'long', day:'2-digit', month:'long', year:'numeric' });
                document.getElementById('viewSessTime').textContent = sess.time + ' Uhr';
                document.getElementById('viewSessLocation').textContent = sess.location || '-';
                document.getElementById('viewSessTrainer').textContent = getUserName(sess.trainerId);
                
                // Teilnehmer Liste View
                const pContainer = document.getElementById('viewSessParticipants');
                pContainer.innerHTML = '';
                (sess.participants || []).forEach(pid => {
                    pContainer.innerHTML += `<span class="inline-flex items-center px-2 py-1 rounded bg-slate-100 text-slate-600 text-xs border border-slate-200">${getUserName(pid)}</span>`;
                });
                document.getElementById('viewSessCount').textContent = (sess.participants || []).length;

                // Buttons logik (Teilnehmen/Absagen)
                const amIParticipant = (sess.participants || []).includes(currentUserId);
                if(amIParticipant) {
                    document.getElementById('btnJoinSession').classList.add('hidden');
                    document.getElementById('btnLeaveSession').classList.remove('hidden');
                } else {
                    document.getElementById('btnJoinSession').classList.remove('hidden');
                    document.getElementById('btnLeaveSession').classList.add('hidden');
                }

                // Populate Edit Form (Hidden)
                document.getElementById('sessId').value = sess.id;
                document.getElementById('sessDate').value = sess.date;
                document.getElementById('sessTime').value = sess.time;
                document.getElementById('sessLocation').value = sess.location;
                document.getElementById('sessCatalogId').value = sess.catalogId;
                setTimeout(() => document.getElementById('sessTrainer').value = sess.trainerId, 0);
                
                // Teilnehmer Checkboxen (für Edit)
                document.getElementById('sessParticipantSearch').value = '';
                const partList = document.getElementById('sessParticipants'); partList.innerHTML = '';
                [...users].sort((a, b) => a.nachname.localeCompare(b.nachname) || a.vorname.localeCompare(b.vorname)).forEach(u => {
                    const isChecked = (sess.participants || []).includes(u.id) ? 'checked' : '';
                    partList.innerHTML += `<label class="flex items-center space-x-2 p-1 hover:bg-slate-50 rounded cursor-pointer"><input type="checkbox" value="${u.id}" ${isChecked} class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 h-4 w-4"><span class="text-sm text-slate-700">${u.nachname}, ${u.vorname}</span></label>`;
                });

                switchToSessionView();
            }
        } else {
            // NEW -> Edit Mode direkt
            document.getElementById('sessId').value = '';
            document.getElementById('sessDate').value = new Date().toISOString().split('T')[0];
            
            // Checkboxen leer
            document.getElementById('sessParticipantSearch').value = '';
            const partList = document.getElementById('sessParticipants'); partList.innerHTML = '';
            [...users].sort((a, b) => a.nachname.localeCompare(b.nachname) || a.vorname.localeCompare(b.vorname)).forEach(u => partList.innerHTML += `<label class="flex items-center space-x-2 p-1 hover:bg-slate-50 rounded cursor-pointer"><input type="checkbox" value="${u.id}" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 h-4 w-4"><span class="text-sm text-slate-700">${u.nachname}, ${u.vorname}</span></label>`);
            
            switchToSessionEdit();
            document.getElementById('btnEditSession').classList.add('hidden'); // Kein zurück zu View bei Neu
        }
        
        document.getElementById('sessionModal').classList.remove('hidden');
    }
    function switchToSessionView() {
        document.getElementById('viewSessionContent').classList.remove('hidden');
        document.getElementById('editSessionContent').classList.add('hidden');
        document.getElementById('viewSessFooter').classList.remove('hidden');
        document.getElementById('editSessFooter').classList.add('hidden');
        document.getElementById('btnEditSession').classList.remove('hidden');
        document.getElementById('sessModalTitle').textContent = "Session Details";
    }

    function switchToSessionEdit() {
        document.getElementById('viewSessionContent').classList.add('hidden');
        document.getElementById('editSessionContent').classList.remove('hidden');
        document.getElementById('viewSessFooter').classList.add('hidden');
        document.getElementById('editSessFooter').classList.remove('hidden');
        document.getElementById('btnEditSession').classList.add('hidden');
        document.getElementById('sessModalTitle').textContent = "Session bearbeiten";
    }

    async function toggleParticipation(join) {
        const sessId = document.getElementById('sessId').value;
        const sess = trainingData.sessions.find(s => s.id == sessId);
        if(!sess) return;
        
        if(join) {
            if(!sess.participants.includes(currentUserId)) sess.participants.push(currentUserId);
        } else {
            sess.participants = sess.participants.filter(id => id !== currentUserId);
        }
        
        await saveTrainingData();
        // Reload Modal Data (einfacher: schließe und öffne neu oder update view)
        openSessionModal(sessId); 
        renderSessions();
    }
    async function saveSession() {
        const id = document.getElementById('sessId').value;
        const parts = Array.from(document.querySelectorAll('#sessParticipants input:checked')).map(cb => cb.value);
        const data = {
            id: id ? parseInt(id) : Date.now(),
            catalogId: document.getElementById('sessCatalogId').value,
            date: document.getElementById('sessDate').value,
            time: document.getElementById('sessTime').value,
            location: document.getElementById('sessLocation').value,
            trainerId: document.getElementById('sessTrainer').value,
            participants: parts
        };

        if(id) {
            const idx = trainingData.sessions.findIndex(s => s.id == id);
            if(idx > -1) trainingData.sessions[idx] = data;
        } else {
            trainingData.sessions.push(data);
        }

        await saveTrainingData();
        document.getElementById('sessionModal').classList.add('hidden');
        renderSessions();
    }

    async function deleteSession() {
        const id = document.getElementById('sessId').value;
        if(confirm("Session löschen?")) {
            trainingData.sessions = trainingData.sessions.filter(s => s.id != id);
            await saveTrainingData();
            document.getElementById('sessionModal').classList.add('hidden');
            renderSessions();
        }
    }

    // --- CATALOG LOGIC ---
    function renderCatalog() {
        const grid = document.getElementById('catalog-grid'); grid.innerHTML = '';
        if(trainingData.catalog.length === 0) { grid.innerHTML = '<div class="col-span-full text-center py-12 bg-slate-50 rounded-xl border border-dashed border-slate-300 text-slate-400">Keine Trainings.</div>'; return; }

        trainingData.catalog.forEach(item => {
            grid.innerHTML += `
                <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200 hover:shadow-md transition-shadow group flex flex-col h-full relative overflow-hidden">
                    <div class="absolute top-0 left-0 w-1 h-full bg-blue-500"></div>
                    <div class="flex justify-between items-start mb-3 pl-2">
                        <span class="px-2 py-0.5 rounded text-[10px] font-bold bg-blue-50 text-blue-700 uppercase tracking-wider border border-blue-100">${item.category || 'Allgemein'}</span>
                        <div class="flex gap-1">
                            <button onclick="openHistoryModal('${item.id}', '${item.title}')" class="text-slate-300 hover:text-purple-600 transition-colors" title="Historie"><span class="material-icons-outlined text-lg">history</span></button>
                            <button onclick="openCatalogModal('${item.id}')" class="text-slate-300 hover:text-blue-600 transition-colors"><span class="material-icons-outlined text-lg">edit</span></button>
                        </div>
                    </div>
                    <h3 class="font-bold text-slate-800 text-lg mb-2 pl-2">${item.title}</h3>
                    <p class="text-sm text-slate-500 line-clamp-3 mb-6 flex-grow pl-2">${item.description}</p>
                    <div class="pt-4 border-t border-slate-100 flex justify-between items-center text-xs text-slate-400 pl-2">
                        <span class="flex items-center gap-1"><span class="material-icons-outlined text-sm">schedule</span> ${item.duration || '?'} Std</span>
                        <button onclick="switchView('dashboard'); openSessionModal()" class="flex items-center gap-1 hover:text-blue-600 transition-colors font-medium">Planen <span class="material-icons-outlined text-sm">arrow_forward</span></button>
                    </div>
                </div>`;
        });
    }

    function openCatalogModal(id = null) {
        document.getElementById('catId').value = '';
        document.getElementById('catTitle').value = '';
        document.getElementById('catDesc').value = '';
        document.getElementById('catDuration').value = '';
        document.getElementById('catModalTitle').textContent = "Neues Training";
        document.getElementById('btnDeleteCatalog').classList.add('hidden');
        document.getElementById('catImageInput').value = ''; // Reset File Input
        document.getElementById('catImagePath').value = '';
        document.getElementById('catImagePreview').classList.add('hidden');

        if(id) {
            const item = trainingData.catalog.find(c => c.id == id);
            if(item) {
                document.getElementById('catId').value = item.id;
                document.getElementById('catTitle').value = item.title;
                document.getElementById('catDesc').value = item.description;
                document.getElementById('catDuration').value = item.duration;
                document.getElementById('catCategory').value = item.category;
                document.getElementById('catModalTitle').textContent = "Training bearbeiten";
                document.getElementById('btnDeleteCatalog').classList.remove('hidden');
            }
            if(item.image) {
                document.getElementById('catImagePath').value = item.image;
                document.getElementById('catImagePreview').src = item.image;
                document.getElementById('catImagePreview').classList.remove('hidden');
        }
        }
        document.getElementById('catalogModal').classList.remove('hidden');
    }

    async function saveCatalogItem() {
        const id = document.getElementById('catId').value;
        // 1. Upload zuerst
        const fileInput = document.getElementById('catImageInput');
        let imagePath = document.getElementById('catImagePath').value;
        
        if(fileInput.files.length > 0) {
            const formData = new FormData();
            formData.append('file', fileInput.files[0]);
            try {
                const res = await fetch('/api/training/upload', { method: 'POST', body: formData });
                if(res.ok) {
                    const data = await res.json();
                    imagePath = data.path;
                }
            } catch(e) { console.error("Upload fail"); }
        }
        
        // 2. Speichern mit Bild-Pfad
        const data = {
            id: id ? parseInt(id) : Date.now(),
            title: document.getElementById('catTitle').value,
            description: document.getElementById('catDesc').value,
            duration: document.getElementById('catDuration').value,
            category: document.getElementById('catCategory').value,
            image: imagePath
        };

        if(id) {
            const idx = trainingData.catalog.findIndex(c => c.id == id);
            if(idx > -1) trainingData.catalog[idx] = data;
        } else {
            trainingData.catalog.push(data);
        }
        await saveTrainingData();
        document.getElementById('catalogModal').classList.add('hidden');
        renderCatalog();
    }

    async function deleteCatalogItem() {
        const id = document.getElementById('catId').value;
        if(confirm("Training löschen?")) {
            trainingData.catalog = trainingData.catalog.filter(c => c.id != id);
            await saveTrainingData();
            document.getElementById('catalogModal').classList.add('hidden');
            renderCatalog();
        }
    }

    // --- HISTORY ---
    function openHistoryModal(catalogId, title) {
        document.getElementById('histTitle').textContent = title;
        const list = document.getElementById('historyList');
        list.innerHTML = '';

        // Finde vergangene Sessions
        const today = new Date().toISOString().split('T')[0];
        const pastSessions = trainingData.sessions.filter(s => s.catalogId == catalogId && s.date < today);
        pastSessions.sort((a,b) => new Date(b.date) - new Date(a.date)); // Neu -> Alt

        if(pastSessions.length === 0) {
            list.innerHTML = '<p class="text-slate-400 italic text-center">Keine vergangenen Sessions.</p>';
        } else {
            pastSessions.forEach(s => {
                const d = new Date(s.date).toLocaleDateString('de-DE');
                const parts = s.participants.map(id => getUserName(id)).join(', ');
                list.innerHTML += `
                    <div class="mb-4 pb-4 border-b border-slate-100 last:border-0">
                        <div class="flex justify-between font-bold text-slate-700 text-sm">
                            <span>${d}</span>
                            <span class="text-slate-500 font-normal">Trainer: ${getUserName(s.trainerId)}</span>
                        </div>
                        <div class="mt-2 text-xs text-slate-600">
                            <strong>Teilnehmer (${s.participants.length}):</strong> ${parts}
                        </div>
                    </div>`;
            });
        }
        document.getElementById('historyModal').classList.remove('hidden');
    }

    // --- COMMONS ---
    async function saveTrainingData() { await fetch('/api/training/data', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(trainingData) }); }
    function getUserName(id) { const u = users.find(x => x.id === id); return u ? `${u.nachname}, ${u.vorname}` : id; }
    function getUserInitials(id) { const u = users.find(x => x.id === id); return u ? (u.vorname[0] + u.nachname[0]).toUpperCase() : '??'; }
</script>
</body>
</html>
"""

def sync_facility_names_to_maintenance(structure_data):
    """
    Synchronisiert Umbenennungen von Facility-Units (3D) zu den Wartungsdaten.
    Basis ist die UUID (facilityUnitId).
    """
    print("--- DEBUG: Starte Sync von Facility-Namen zu Maintenance ---")
    try:
        # 1. Map aufbauen: ID -> Voller Name (Index + Name)
        id_to_name_map = {}

        def build_map(nodes, prefix=""):
            for i, node in enumerate(nodes):
                current_index_str = f"{prefix}{i + 1}"
                full_name = f"{current_index_str} {node.get('name', '')}"
                if node.get('id'):
                    id_to_name_map[node['id']] = full_name

                if 'children' in node:
                    build_map(node['children'], current_index_str + ".")

        build_map(structure_data.get('betriebseinheiten', []))
        build_map(structure_data.get('gebaeude', []))

        # 2. Wartungsdaten laden
        m_data = load_maintenance_data()
        definitions = m_data.get('definitions', [])
        orders = m_data.get('orders', [])

        changes_defs = False
        changes_orders = False

        # 3. Definitionen aktualisieren
        for d in definitions:
            f_id = d.get('facilityUnitId')
            if f_id and f_id in id_to_name_map:
                current_name_in_def = d.get('plantIndex')
                new_name = id_to_name_map[f_id]

                if current_name_in_def != new_name:
                    print(f"INFO: Aktualisiere Wartungsplan {d.get('id')}: '{current_name_in_def}' -> '{new_name}'")
                    d['plantIndex'] = new_name
                    changes_defs = True

        # 4. Aufträge aktualisieren (Optional, aber gut für Konsistenz offener Aufträge)
        for o in orders:
            # Nur offene Aufträge aktualisieren? Oder alle?
            # Da wir hier UUIDs nutzen, ist es sicher, auch historische zu ändern,
            # aber für die Historie ist der alte Name oft besser.
            # Empfehlung: Nur offene (nicht Completed).
            if o.get('status') == 'Completed': continue

            f_id = o.get('facilityUnitId')
            if f_id and f_id in id_to_name_map:
                current_name = o.get('plantIndex')
                new_name = id_to_name_map[f_id]

                if current_name != new_name:
                    print(f"INFO: Aktualisiere Auftrag {o.get('id')}: '{current_name}' -> '{new_name}'")
                    o['plantIndex'] = new_name
                    changes_orders = True

        # 5. Speichern
        if changes_defs:
            save_maintenance_definitions(definitions)

        if changes_orders:
            save_maintenance_orders(orders)

        if changes_defs or changes_orders:
            print("--- DEBUG: Wartungsdaten erfolgreich synchronisiert. ---")
        else:
            print("--- DEBUG: Keine Namensänderungen erforderlich. ---")

    except Exception as e:
        print(f"!!! FEHLER beim Facility-Maintenance-Sync: {e}")
        traceback.print_exc()


def sync_trolleys_and_storage(hardware_data):
    """
    1. Aktualisiert Namen/Daten in Rüstwagen/Lagern basierend auf IDs (Sync).
    2. Entfernt Items aus alten Orten, wenn sie verschoben wurden (Clean-Up).
    3. Entfernt Items komplett, wenn sie im Master-Inventar gelöscht wurden (Hard Delete).
    """
    print("--- DEBUG: Starte Sync & Cleanup für Trolleys/Storage ---")

    trolleys = hardware_data.get('trolleys', {})
    storage = hardware_data.get('storage_units', {})
    inventory = hardware_data.get('inventory', {})

    id_to_part_map = {}
    id_to_location_map = {}

    for pn, part in inventory.items():
        for s in part.get('serials', []):
            if s.get('id'):
                id_to_part_map[s['id']] = {'name': part.get('name', ''), 'pn': pn, 'sn': s.get('sn', '')}
                id_to_location_map[s['id']] = s.get('location', '')

    changes = False

    def process_containers(containers_dict):
        nonlocal changes
        for container_name, container_data in containers_dict.items():
            new_items = []
            for item in container_data.get('items', []):
                item_id = item.get('id')

                # Fall 1: Manuelles Item (Keine ID) -> Behalten
                if not item_id:
                    new_items.append(item)
                    continue

                # Fall 2: Verknüpftes Item

                # Check A: Existiert das Item noch in der DB?
                if item_id not in id_to_part_map:
                    # --- ÄNDERUNG: HARD DELETE ---
                    # Item im Master gelöscht -> Hier auch löschen!
                    print(f"INFO: Entferne gelöschtes Item (ID: {item_id}) aus {container_name}")
                    changes = True
                    continue  # NICHT hinzufügen

                # Check B: Ist das Item HIER am richtigen Ort?
                real_location = id_to_location_map.get(item_id)
                if real_location != container_name:
                    print(f"INFO: Entferne Item {item.get('sn')} aus {container_name} (ist jetzt in {real_location})")
                    changes = True
                    continue

                    # Check C: Daten-Update
                live_data = id_to_part_map[item_id]
                if (item.get('name') != live_data['name'] or
                        item.get('pn') != live_data['pn'] or
                        item.get('sn') != live_data['sn']):
                    item['name'] = live_data['name']
                    item['pn'] = live_data['pn']
                    item['sn'] = live_data['sn']
                    changes = True

                new_items.append(item)

            container_data['items'] = new_items

    process_containers(trolleys)
    process_containers(storage)

    if changes:
        save_test_equipment_data_to_file(hardware_data)
        print("--- DEBUG: Trolleys/Storage erfolgreich synchronisiert und bereinigt. ---")

def sync_hardware_names_to_facility(hardware_data):
    """
    Aktualisiert Hardware-Namen in der ANLAGENSTRUKTUR (structure.json).
    Arbeitet rekursiv durch den Baum.
    """
    print("--- DEBUG: Starte Sync von Hardware-Namen zu Facility-Struktur ---")
    try:
        # 1. Lookup-Maps aus hardware_data aufbauen — reines In-Memory, kein Lock nötig
        id_to_label_map = {}
        sn_to_id_map = {}
        inventory = hardware_data.get('inventory', {})

        for pn, item in inventory.items():
            part_name = item.get('name', 'Unbekannt')
            for serial_entry in item.get('serials', []):
                hw_id = serial_entry.get('id')
                sn = serial_entry.get('sn')
                if hw_id and sn:
                    display_label = f"{sn} ({pn} - {part_name})"
                    id_to_label_map[hw_id] = display_label
                    sn_to_id_map[sn] = hw_id

        if not os.path.exists(FACILITY_STRUCTURE_FILE):
            return

        # Bug 7 Fix: Read-Modify-Write atomar unter per-file-Lock — verhindert Race mit
        # /api/facility/save_structure, das ebenfalls structure.json per save_json_file überschreibt.
        # Ohne Lock könnte ein 3D-View-Save zwischen unserem Read und Write liegen und
        # anschließend von unserem Write mit veralteten Struktur-Daten überschrieben werden.
        with _get_file_lock(FACILITY_STRUCTURE_FILE):
            structure_data = _load_json_file_unlocked(FACILITY_STRUCTURE_FILE, default_val={})
            if not isinstance(structure_data, dict):
                return

            changes_made = False

            # Rekursive Funktion zum Aktualisieren eines Knotens und seiner Kinder
            def update_node_hardware(nodes):
                node_changed = False
                for node in nodes:
                    # Hardware-Liste des aktuellen Knotens prüfen
                    if 'hardware' in node and isinstance(node['hardware'], list):
                        new_hw_list = []
                        for hw_str in node['hardware']:
                            if not hw_str: continue

                            current_id = None
                            old_label = None

                            # A) JSON Parsing
                            if hw_str.strip().startswith('{'):
                                try:
                                    asset_obj = json.loads(hw_str)
                                    current_id = asset_obj.get('id')
                                    old_label = asset_obj.get('label')
                                except:
                                    pass

                            # B) Legacy Fallback
                            if not current_id:
                                old_label = hw_str
                                current_sn = hw_str.split(' ')[0]
                                current_id = sn_to_id_map.get(current_sn)

                            # Update Logik
                            if current_id and current_id in id_to_label_map:
                                new_label = id_to_label_map[current_id]
                                if new_label != old_label or not hw_str.strip().startswith('{'):
                                    new_hw_json = json.dumps({"id": current_id, "label": new_label})
                                    new_hw_list.append(new_hw_json)
                                    node_changed = True
                                    print(f"INFO: Aktualisiere Facility-Node '{node.get('name')}': '{old_label}' -> '{new_label}'")
                                else:
                                    new_hw_list.append(hw_str)

                            elif current_id and current_id not in id_to_label_map:
                                node_changed = True
                                print(f"INFO: Entferne gelöschtes Asset '{old_label}' aus Facility-Node '{node.get('name')}'")

                            else:
                                new_hw_list.append(hw_str)

                        if node_changed:
                            node['hardware'] = new_hw_list

                    # Rekursion für Kinder
                    if 'children' in node and isinstance(node['children'], list):
                        if update_node_hardware(node['children']):
                            node_changed = True

                return node_changed

            # 3. Beide Bäume durchlaufen
            if update_node_hardware(structure_data.get('betriebseinheiten', [])): changes_made = True
            if update_node_hardware(structure_data.get('gebaeude', [])): changes_made = True

            # 4. Speichern (innerhalb des Locks, damit kein anderer Write dazwischen passt)
            if changes_made:
                if _write_json_file_unlocked(FACILITY_STRUCTURE_FILE, structure_data):
                    print("--- DEBUG: Facility-Struktur erfolgreich synchronisiert. ---")
                else:
                    print("!!! FEHLER: Konnte Facility-Struktur nicht speichern.")
            else:
                print("--- DEBUG: Keine Änderungen in Facility-Struktur nötig. ---")

    except Exception as e:
        print(f"!!! FEHLER beim Facility-Sync: {e}")
        traceback.print_exc()

def sync_hardware_names_to_definitions(hardware_data):
    """
    (Version 2) Aktualisiert die Anzeigenamen in den Wartungs-DEFINITIONEN.
    Unterstützt sowohl neue JSON-String-Assets als auch alte Plain-String-Assets
    und migriert alte bei Bedarf.
    """
    print("--- DEBUG: Starte Sync von Hardware-Namen zu Wartungs-Definitionen (Version 2) ---")
    try:
        # 1. Zwei Lookup-Maps erstellen:
        # a) ID -> "SN (PN - Name)" (Primärer Mechanismus)
        # b) SN -> ID (Für die Migration von Altdaten)
        id_to_label_map = {}
        sn_to_id_map = {}
        inventory = hardware_data.get('inventory', {})

        for pn, item in inventory.items():
            part_name = item.get('name', 'Unbekannt')
            for serial_entry in item.get('serials', []):
                hw_id = serial_entry.get('id')
                sn = serial_entry.get('sn')
                if hw_id and sn:
                    display_label = f"{sn} ({pn} - {part_name})"
                    id_to_label_map[hw_id] = display_label
                    sn_to_id_map[sn] = hw_id

        # 2. Definitionen laden
        definitions_data = []
        if os.path.exists(MAINTENANCE_DEFINITIONS_FILE):
            with open(MAINTENANCE_DEFINITIONS_FILE, 'r', encoding='utf-8') as f:
                definitions_data = json.load(f)

        if not definitions_data:
            return

        changes_made = False

        # 3. Durch Definitionen iterieren und Assets prüfen/aktualisieren
        for definition in definitions_data:
            if 'maintenanceObjects' in definition and isinstance(definition['maintenanceObjects'], list):
                new_asset_list = []
                for asset_str in definition['maintenanceObjects']:
                    if not asset_str: continue

                    current_id = None
                    old_label = None

                    # A) Versuche, als neuer JSON-String zu parsen
                    if asset_str.strip().startswith('{'):
                        try:
                            asset_obj = json.loads(asset_str)
                            current_id = asset_obj.get('id')
                            old_label = asset_obj.get('label')
                        except json.JSONDecodeError:
                            # Ungültiges JSON, behandle es als alten String
                            pass

                    # B) Fallback: Behandle als alten Plain-String
                    if not current_id:
                        old_label = asset_str
                        current_sn = asset_str.split(' ')[0]
                        current_id = sn_to_id_map.get(current_sn)

                    # Wenn wir eine ID haben, können wir den aktuellen Namen nachschlagen
                    if current_id and current_id in id_to_label_map:
                        new_label = id_to_label_map[current_id]
                        # Wenn sich das Label geändert hat ODER wir einen alten String migrieren
                        if new_label != old_label or not asset_str.strip().startswith('{'):
                            # Erstelle IMMER den neuen, korrekten JSON-String
                            new_asset_json_str = json.dumps({"id": current_id, "label": new_label})
                            new_asset_list.append(new_asset_json_str)
                            changes_made = True
                            print(
                                f"INFO: Aktualisiere Asset in Def {definition.get('id')}: '{old_label}' -> '{new_label}' (migriert zu JSON)")
                        else:
                            new_asset_list.append(asset_str)  # Keine Änderung
                    else:
                        # Asset nicht (mehr) im Inventar gefunden, behalte den alten Wert
                        new_asset_list.append(asset_str)

                definition['maintenanceObjects'] = new_asset_list

        # 4. Speichern, falls Änderungen vorliegen
        if changes_made:
            if save_maintenance_definitions(definitions_data):
                print("--- DEBUG: Wartungs-Definitionen erfolgreich mit neuen Namen aktualisiert. ---")
            else:
                print("!!! FEHLER: Konnte die aktualisierten Definitionen nicht speichern.")
        else:
            print("--- DEBUG: Keine Namensänderungen in den Definitionen erforderlich. ---")

    except Exception as e:
        print(f"!!! FEHLER beim Sync der Hardware-Namen: {e}")
        traceback.print_exc()

def init_facility_data():
    """Erstellt Ordner und Standard-Strukturdatei, falls nicht vorhanden."""
    if not os.path.exists(FACILITY_DATA_DIR):
        os.makedirs(FACILITY_DATA_DIR)

    if not os.path.exists(FACILITY_MODELS_DIR):
        os.makedirs(FACILITY_MODELS_DIR)

    if not os.path.exists(FACILITY_BACKUP_DIR):
        os.makedirs(FACILITY_BACKUP_DIR)

    if not os.path.exists(FACILITY_ATTACHMENTS_DIR):
        os.makedirs(FACILITY_ATTACHMENTS_DIR)

    if not os.path.exists(FACILITY_STRUCTURE_FILE):
        print(f"INFO: Erstelle leere {FACILITY_STRUCTURE_FILE}")
        try:
            with open(FACILITY_STRUCTURE_FILE, 'w', encoding='utf-8') as f:
                json.dump(FACILITY_EMPTY_DATA, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(f"FEHLER beim Initialisieren der Facility-Daten: {e}")

def load_test_equipment_data():
    """Lädt Hardware-Daten."""
    # 1. Wenn Datei existiert, versuche sie zu laden
    if os.path.exists(TEST_EQUIPMENT_DATA_FILE):
        try:
            with open(TEST_EQUIPMENT_DATA_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"KRITISCHER FEHLER beim Laden der Test-Equipment Daten: {e}")
            return None

    # 2. Nur wenn Datei NICHT existiert (erster Start), Standardwerte laden
    # Fallback, falls Datei existiert, aber alt ist
    data = json.load(f)
    if "storage_units" not in data:
        data["storage_units"] = {}
    return data

    print("INFO: Keine Hardware-Datei gefunden. Lade Standardwerte.")
    return {
        "inventory": {}, "boms": {}, "mappings": {}, "trolleys": {},
        "storage_units": {}
    }


def save_test_equipment_data_to_file(data):
    """Speichert Hardware-Daten atomar."""
    print("--- DEBUG: save_test_equipment_data_to_file (Atomic) gestartet ---")

    success = save_json_file(TEST_EQUIPMENT_DATA_FILE, data)

    if success:
        print("--- DEBUG: Hardware-Datei erfolgreich und sicher gespeichert. ---")
    else:
        print("!!! DEBUG: Fehler beim Speichern der Hardware-Daten.")

    return success


def compute_hardware_data_hash(data):
    """Berechnet einen MD5-Hash über den Hardware-Datensatz (für Konfliktdetektierung)."""
    serialized = json.dumps(data, sort_keys=True, ensure_ascii=False)
    return hashlib.md5(serialized.encode('utf-8')).hexdigest()


def get_current_hardware_file_hash():
    """Liest die aktuelle Hardware-Datei und gibt ihren Hash zurück (None bei Fehler)."""
    try:
        with open(TEST_EQUIPMENT_DATA_FILE, 'r', encoding='utf-8') as f:
            raw = f.read()
        data = json.loads(raw)
        return compute_hardware_data_hash(data)
    except Exception:
        return None


def load_completeness_log():
    """Lädt das zentrale Vollständigkeitsprotokoll."""
    if os.path.exists(COMPLETENESS_LOG_FILE):
        try:
            with open(COMPLETENESS_LOG_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"Fehler beim Laden des Completeness Logs: {e}")
    return {"entries": []}


def save_completeness_log(data):
    """Speichert das Vollständigkeitsprotokoll atomar."""
    return save_json_file(COMPLETENESS_LOG_FILE, data)


def migrate_completeness_log():
    """
    Einmalige Migration: verschiebt completeness_log-Arrays aus
    test_equipment_data.json in die separate completeness_log.json.
    Wird übersprungen wenn die Zieldatei bereits existiert.
    """
    if os.path.exists(COMPLETENESS_LOG_FILE):
        return

    te_data = load_test_equipment_data()
    if not te_data:
        save_completeness_log({"entries": []})
        return

    entries = []
    modified = False

    for container_type, data_dict in [('trolley', te_data.get('trolleys', {})),
                                       ('storage', te_data.get('storage_units', {}))]:
        for container_name, container_data in data_dict.items():
            log = container_data.pop('completeness_log', None)
            if log:
                modified = True
                for entry in log:
                    entry['container'] = container_name
                    entry['container_type'] = container_type
                    entries.append(entry)

    save_completeness_log({"entries": entries})
    if modified:
        save_test_equipment_data_to_file(te_data)
    print(f"INFO: Completeness Log migriert — {len(entries)} Einträge in {COMPLETENESS_LOG_FILE}.")


def _update_last_checked_snapshot(entry):
    """Aktualisiert lastCheckedBy/At im Trolley-/Storage-Objekt für Schnellstatus."""
    try:
        te_data = load_test_equipment_data()
        if not te_data:
            return
        db = te_data.get('trolleys' if entry.get('container_type') == 'trolley' else 'storage_units', {})
        name = entry.get('container')
        if name in db:
            db[name]['lastCheckedBy'] = entry.get('checkedBy', '')
            db[name]['lastCheckedAt'] = entry.get('timestamp', '')
            save_test_equipment_data_to_file(te_data)
    except Exception as e:
        print(f"Fehler beim Snapshot-Update: {e}")


def load_completeness_config():
    """Lädt die Prüfkonfiguration (welche Container, welche Häufigkeit)."""
    if os.path.exists(COMPLETENESS_CONFIG_FILE):
        try:
            with open(COMPLETENESS_CONFIG_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"Fehler beim Laden der Completeness Config: {e}")
    return {"containers": {}}


def save_completeness_config(data):
    return save_json_file(COMPLETENESS_CONFIG_FILE, data)


def load_project_data_monitor():
    """
    Liest alle JSON-Dateien aus dem PROJECT_STATUS_DIR, verarbeitet sie
    und gibt sie in einer für das Template aufbereiteten Form zurück.
    """
    projects = []
    if not os.path.exists(PROJECT_STATUS_DIR):
        return []

    for filename in os.listdir(PROJECT_STATUS_DIR):
        if filename.endswith(".json"):
            filepath = os.path.join(PROJECT_STATUS_DIR, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    processed_project = process_single_project_monitor(data)
                    projects.append(processed_project)
            except Exception as e:
                print(f"Fehler beim Laden oder Verarbeiten der Datei {filename}: {e}")

    projects.sort(key=lambda p: (p['status'] == 'completed', p.get('due_date_obj', datetime.max)))
    return projects

def process_single_project_monitor(project_json):
    """
    Bereitet ein einzelnes Projekt-JSON für das Frontend-Template auf.
    """
    status = "completed" if project_json.get("progress_percent") == 100 else "active"
    all_steps = project_json.get("steps", [])

    step_map = {s['id_str']: s for s in all_steps}
    main_steps = []

    for step in step_map.values():
        step['children'] = []
        step['display_children'] = []

    for step in step_map.values():
        parent_id = step.get("parent_id_str")
        if parent_id and parent_id in step_map:
            step_map[parent_id]['children'].append(step)
        elif not parent_id:
            main_steps.append(step)

    for step in all_steps:
        step['children'].sort(key=lambda s: [int(p) for p in s['id_str'].split('.')])
        step['display_children'] = [
            child for child in step['children']
            if child.get('type') not in ['troubleshooting', 'solution']
        ]
        for dev in step.get('deviations', []):
            step['display_children'].append({
                'type': 'deviation',
                'status': 'Completed' if dev.get('status') == 'Closed' else 'Open',
                'deviation_details': {
                    'reason': dev.get('reason', ''),
                    'hub_incident_number': dev.get('hub_incident_nr', '')
                },
                'display_children': [],
                'children': []
            })

    main_steps.sort(key=lambda s: [int(p) for p in s['id_str'].split('.')])

    def check_if_in_progress(step):
        if step.get('status') == 'In Progress':
            return True
        return any(check_if_in_progress(child) for child in step.get('children', []))

    def check_if_on_hold(step):
        if step.get('status') == 'On Hold':
            return True
        return any(check_if_on_hold(child) for child in step.get('children', []))

    for main_step in main_steps:
        main_step['is_in_progress'] = check_if_in_progress(main_step)
        main_step['is_on_hold'] = check_if_on_hold(main_step) and not main_step['is_in_progress']
        flex_weight = 0.5
        if main_step.get('display_children'):
            flex_weight = max(1, len(main_step['display_children']))
            if "test" in main_step.get('name', '').lower():
                flex_weight = max(flex_weight, 3)
        main_step['flex_weight'] = flex_weight

        main_step['collapsed_dots'] = []
        for child in main_step.get('children', []):
            dot = {'class': 'bg-gray-300'}
            if child.get('status') == 'Completed':
                dot['class'] = 'bg-primary'
            elif check_if_in_progress(child):
                dot['class'] = 'border-2 border-primary-active'
            elif check_if_on_hold(child):
                dot['class'] = 'bg-yellow-400'
            if child.get('type') == 'deviation' and child.get('status') != 'Completed':
                dot['class'] = 'bg-danger'
            main_step['collapsed_dots'].append(dot)
        def count_open_new_devs(step):
            count = sum(1 for d in step.get('deviations', []) if d.get('status') != 'Closed')
            for child in step.get('children', []):
                count += count_open_new_devs(child)
            return count
        for _ in range(count_open_new_devs(main_step)):
            main_step['collapsed_dots'].append({'class': 'bg-danger'})

    due_date_obj = datetime.max
    try:
        due_date_str = project_json.get("due_date_display")
        if due_date_str and due_date_str != "N/A":
            due_date_obj = datetime.strptime(due_date_str.split(',')[0], "%d. %b %Y")
    except (ValueError, TypeError):
        pass

    return {
        "project_code": project_json.get('project_code', 'N/A'),
        "engine_serial": project_json.get('engine_serial', 'N/A'),
        "customer": project_json.get('customer', 'N/A'),
        "engine_type": project_json.get("engine_type", "N/A"),
        "progress": project_json.get("progress_percent", 0),
        "status": status,
        "due_date_obj": due_date_obj,
        "main_steps": main_steps
    }


def parse_date_from_string(date_str: str, format_str: str = "%Y-%m-%d") -> Optional[date]:
    if not date_str: return None
    try:
        return datetime.strptime(date_str, format_str).date()
    except ValueError:
        try:
            return datetime.strptime(date_str, "%d.%m.%Y").date()
        except ValueError:
            try:
                return datetime.strptime(date_str.split()[0], "%d.%m.%Y").date()
            except (ValueError, IndexError):
                # print(f"Warnung: Konnte Datum '{date_str}' nicht parsen.")
                return None


def format_date_to_string(date_obj: Optional[date], format_str: str = "%Y-%m-%d") -> str:
    if isinstance(date_obj, date):
        return date_obj.strftime(format_str)
    return ""


def get_datetime_from_date_shift(date_obj: Optional[date], shift_str: str) -> Optional[datetime]:
    if not isinstance(date_obj, date):
        return None
    hour_val = TIME_MAPPING.get(shift_str.upper())
    if hour_val is None:
        return None
    try:
        time_obj_val = time(hour_val, 0)
        return datetime.combine(date_obj, time_obj_val)
    except Exception as e:
        print(f"Fehler in get_datetime_from_date_shift: {e}")
        return None


def load_timeline_projects_for_flask(file_paths: List[str]) -> List[Dict]:
    """Lädt Projekte aus einer oder mehreren CSV-Dateien."""
    loaded_projects = []
    # Set, um Duplikate anhand von (project_code, engine_serial) zu vermeiden
    seen_projects = set()

    if not isinstance(file_paths, list):
        file_paths = [file_paths]

    for file_path in file_paths:
        if not os.path.exists(file_path):
            print(f"WARNUNG: Projektdatei {file_path} nicht gefunden, wird übersprungen.")
            continue
        try:
            with open(file_path, newline='', encoding='utf-8-sig') as csvfile:
                reader = csv.reader(csvfile)
                for row_num, row in enumerate(reader, start=1):
                    if not row or not any(field.strip() for field in row) or len(row) < 10:
                        continue

                    project_code = row[2].strip()
                    engine_serial = row[3].strip()
                    project_key = (project_code, engine_serial)

                    if project_key in seen_projects:
                        continue  # Überspringe Duplikat
                    seen_projects.add(project_key)

                    project_name, engine_type, _, _, customer, be_str, ts_str, te_str, sa_str, active_str = [r.strip()
                                                                                                             for r in
                                                                                                             row[:10]]
                    active = active_str == '1'
                    events_data = row[10:]
                    events = []
                    for i in range(0, len(events_data), 6):
                        if i + 5 >= len(events_data):
                            break
                        title, start_str_csv, start_shift, end_str_csv, end_shift, color = [events_data[i + j].strip()
                                                                                            for j in range(6)]
                        if not title: continue
                        start_date_obj = parse_date_from_string(start_str_csv)
                        end_date_obj = parse_date_from_string(end_str_csv)
                        if not start_date_obj or not end_date_obj: continue
                        events.append({'title': title, 'start': format_date_to_string(start_date_obj),
                                       'start_shift': start_shift.upper() or 'AM',
                                       'end': format_date_to_string(end_date_obj),
                                       'end_shift': end_shift.upper() or 'PM', 'color': color or 'blue'})

                    loaded_projects.append(
                        {'name': project_name, 'engine_type': engine_type, 'project_code': project_code,
                         'engine_serial': engine_serial, 'customer': customer, 'BE': be_str, 'TS': ts_str, 'TE': te_str,
                         'SA': sa_str, 'active': active, 'events': events})
            print(f"INFO: {len(loaded_projects)} Projekte aus {file_paths} geladen.")
        except Exception as e:
            print(f"FEHLER beim Lesen der Projekt-CSV-Datei ({file_path}): {e}")
            traceback.print_exc()

    return loaded_projects


def save_projects_for_flask(file_path: str, projects: List[Dict], silent: bool = True):
    try:
        with open(file_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
            writer = csv.writer(csvfile)
            for project in projects:
                row_data = [
                    project.get('name', ''), project.get('engine_type', ''), project.get('project_code', ''),
                    project.get('engine_serial', ''), project.get('customer', ''), project.get('BE', ''),
                    project.get('TS', ''), project.get('TE', ''), project.get('SA', ''),
                    '1' if project.get('active', True) else '0'
                ]
                for event in project.get('events', []):
                    start_date_obj = parse_date_from_string(event.get('start', ''))
                    end_date_obj = parse_date_from_string(event.get('end', ''))
                    row_data.extend([
                        event.get('title', ''), format_date_to_string(start_date_obj),
                        event.get('start_shift', 'AM').upper(), format_date_to_string(end_date_obj),
                        event.get('end_shift', 'PM').upper(), event.get('color', 'blue')
                    ])
                writer.writerow(row_data)
        if not silent:
            print(f"INFO: Projekte erfolgreich in {file_path} gespeichert.")
        return True
    except Exception as e:
        print(f"FEHLER beim Speichern der Projekt-CSV-Datei ({file_path}): {e}")
        traceback.print_exc()
        return False


def load_qualifications_from_csv(file_path, parent=None):
    global MERKMALE_IN_ORDER
    qualifications = []

    if not os.path.exists(file_path):
        print(f"WARNUNG: Qualifikationsdatei {file_path} nicht gefunden.")
        if not MERKMALE_IN_ORDER:
            MERKMALE_IN_ORDER = ["CFM56/Mechanic", "PW1500G/Operator"]  # Fallback
        return [], MERKMALE_IN_ORDER

    try:
        with open(file_path, newline='', encoding='utf-8-sig') as csvfile:
            reader = csv.DictReader(csvfile)
            header = reader.fieldnames

            if not header:
                print(f"WARNUNG: Die Qualifikationsdatei {file_path} ist leer oder hat keinen Header.")
                return [], []

            fixed_cols = ['Quali', 'Name', 'ID']
            local_merkmale_in_order = [h for h in header if h not in fixed_cols and '_' not in h]

            for row_num, row in enumerate(reader, start=2):
                if not any(row.values()):
                    continue

                merkmale_list = []
                for merkmal_name in local_merkmale_in_order:
                    status_raw = row.get(merkmal_name)
                    start_date_raw = row.get(f'{merkmal_name}_Start')
                    end_date_raw = row.get(f'{merkmal_name}_End')
                    cert_path_raw = row.get(f'{merkmal_name}_Cert')

                    status = status_raw.strip() if isinstance(status_raw, str) else ""
                    start_date = start_date_raw.strip() if isinstance(start_date_raw, str) else ""
                    end_date = end_date_raw.strip() if isinstance(end_date_raw, str) else ""
                    cert_path = cert_path_raw.strip() if isinstance(cert_path_raw, str) else ""

                    # NEU: Dynamischen Status berechnen
                    dynamic_status = calculate_dynamic_status(status, start_date, end_date)

                    merkmale_list.append({
                        'merkmal': merkmal_name,
                        'wert': dynamic_status,  # <-- Hier wird der dynamische Wert verwendet
                        'start_date': start_date,
                        'end_date': end_date,
                        'cert_path': cert_path
                    })

                user_id = row.get('ID', '').strip()
                if not user_id:
                    print(f"WARNUNG: Zeile {row_num} in {file_path} wird übersprungen, da keine ID vorhanden ist.")
                    continue

                qualifications.append({
                    'quali': row.get('Quali', '').strip(),
                    'name': row.get('Name', '').strip(),
                    'id': row.get('ID', '').strip().lower(),  # <-- KORREKTUR
                    'merkmale': merkmale_list
                })

        if local_merkmale_in_order:
            MERKMALE_IN_ORDER = local_merkmale_in_order
        elif not MERKMALE_IN_ORDER:
            MERKMALE_IN_ORDER = ["CFM56/Mechanic", "PW1500G/Operator"]
            print(f"WARNUNG: Keine Merkmale in CSV gefunden, verwende Default-Merkmale: {MERKMALE_IN_ORDER}")

        print(
            f"INFO (korrigiert): {len(qualifications)} Qualifikationen aus '{file_path}' geladen. Identifizierte Merkmale: {len(MERKMALE_IN_ORDER)}")
        return qualifications, MERKMALE_IN_ORDER
    except Exception as e:
        print(f"FEHLER beim Laden der Qualifikationen (Version 4): {e}")
        traceback.print_exc()
        if not MERKMALE_IN_ORDER: MERKMALE_IN_ORDER = ["CFM56/Mechanic", "PW1500G/Operator"]
        return [], MERKMALE_IN_ORDER


def parse_german_date_from_header(date_str_raw, year_from_kw_header_suffix, fallback_year):
    date_str_cleaned = date_str_raw.replace('\n', ' ').strip()
    day_month_match = re.search(r'(\d{2})\.(\d{2})', date_str_cleaned)
    if not day_month_match:
        return None

    day = int(day_month_match.group(1))
    month = int(day_month_match.group(2))

    year_to_use_int = 0

    year_in_date_str_match = re.search(r'(?:Jan|Feb|Mär|Mrz|Apr|Mai|Jun|Jul|Aug|Sep|Okt|Nov|Dez)\s*(\d{2})',
                                       date_str_cleaned, re.IGNORECASE)
    if year_in_date_str_match:
        year_to_use_int = int("20" + year_in_date_str_match.group(1))
    elif year_from_kw_header_suffix:
        year_to_use_int = int("20" + year_from_kw_header_suffix)
    else:
        year_to_use_int = fallback_year

    try:
        return date(year_to_use_int, month, day)
    except ValueError:
        try:
            if month == 12 and date(year_to_use_int - 1, month, day): return date(year_to_use_int - 1, month, day)
            if month == 1 and date(year_to_use_int + 1, month, day): return date(year_to_use_int + 1, month, day)
        except ValueError:
            pass
        return None


def _easter(year):
    """Berechnet Ostersonntag nach dem anonymen Gregorianischen Algorithmus."""
    a = year % 19
    b, c = divmod(year, 100)
    d, e = divmod(b, 4)
    f = (b + 8) // 25
    g = (b - f + 1) // 3
    h = (19 * a + b - d - g + 15) % 30
    i, k = divmod(c, 4)
    l = (32 + 2 * e + 2 * i - h - k) % 7
    m = (a + 11 * h + 22 * l) // 451
    month, day = divmod(114 + h + l - 7 * m, 31)
    return date(year, month, day + 1)


def _brandenburg_holidays(year):
    """Gesetzliche Feiertage in Brandenburg für ein gegebenes Jahr."""
    easter = _easter(year)
    td = timedelta
    fixed = [
        (date(year, 1, 1),   "Neujahrstag"),
        (date(year, 5, 1),   "Tag der Arbeit"),
        (date(year, 10, 3),  "Tag der Deutschen Einheit"),
        (date(year, 10, 31), "Reformationstag"),
        (date(year, 12, 25), "1. Weihnachtstag"),
        (date(year, 12, 26), "2. Weihnachtstag"),
    ]
    moveable = [
        (easter - td(days=2),  "Karfreitag"),
        (easter,               "Ostersonntag"),
        (easter + td(days=1),  "Ostermontag"),
        (easter + td(days=39), "Christi Himmelfahrt"),
        (easter + td(days=49), "Pfingstsonntag"),
        (easter + td(days=50), "Pfingstmontag"),
    ]
    return [(d.isoformat(), name) for d, name in sorted(fixed + moveable)]


def get_holidays_set():
    """Brandenburger Feiertage ± 2 Jahre + firmeninterne Schließtage."""
    result = {}
    today = date.today()
    for year in range(today.year - 1, today.year + 3):
        for date_str, name in _brandenburg_holidays(year):
            result[date_str] = name
    for entry in load_company_closures():
        result[entry['date']] = entry['name']
    return result


def load_company_closures():
    if not os.path.exists(ATTENDANCE_CLOSURES_FILE):
        return []
    try:
        with open(ATTENDANCE_CLOSURES_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception:
        return []


def save_company_closures(closures):
    with _get_file_lock(ATTENDANCE_CLOSURES_FILE):
        os.makedirs(ATTENDANCE_DIR, exist_ok=True)
        with open(ATTENDANCE_CLOSURES_FILE, 'w', encoding='utf-8') as f:
            json.dump(sorted(closures, key=lambda x: x['date']), f, ensure_ascii=False, indent=2)


def load_attendance_groups():
    if not os.path.exists(ATTENDANCE_GROUPS_FILE):
        return []
    try:
        with open(ATTENDANCE_GROUPS_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"FEHLER beim Laden der Anwesenheits-Gruppen: {e}")
        return []


def save_attendance_groups(groups):
    with _get_file_lock(ATTENDANCE_GROUPS_FILE):
        os.makedirs(ATTENDANCE_DIR, exist_ok=True)
        with open(ATTENDANCE_GROUPS_FILE, 'w', encoding='utf-8') as f:
            json.dump(groups, f, ensure_ascii=False, indent=2)


def load_vacation_quotas():
    if not os.path.exists(ATTENDANCE_QUOTAS_FILE):
        return {}
    try:
        with open(ATTENDANCE_QUOTAS_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"FEHLER beim Laden der Urlaubsquoten: {e}")
        return {}


def save_vacation_quotas(quotas):
    with _get_file_lock(ATTENDANCE_QUOTAS_FILE):
        os.makedirs(ATTENDANCE_DIR, exist_ok=True)
        with open(ATTENDANCE_QUOTAS_FILE, 'w', encoding='utf-8') as f:
            json.dump(quotas, f, ensure_ascii=False, indent=2)


def load_raw_attendance_data():
    if not os.path.exists(ATTENDANCE_DATA_FILE):
        return {"employees": [], "last_updated": None}
    try:
        with open(ATTENDANCE_DATA_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"FEHLER beim Laden der Anwesenheitsdaten: {e}")
        return {"employees": [], "last_updated": None}


def save_raw_attendance_data(data):
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        os.makedirs(ATTENDANCE_DIR, exist_ok=True)
        data['last_updated'] = datetime.now().isoformat()
        with open(ATTENDANCE_DATA_FILE, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2, default=str)


def load_employees_from_attendance_json():
    global ALL_EMPLOYEES
    data = load_raw_attendance_data()
    lib_lookup = {m['id']: m for m in data.get('shift_model_library', [])}
    holidays_map = get_holidays_set()
    employees = []
    for emp_data in data.get('employees', []):
        employee_dict = {
            'name': emp_data.get('name', ''),
            'initials': emp_data.get('initials', ''),
            'quali': emp_data.get('group_id', ''),
            'normalized_name': emp_data.get('normalized_name', ''),
            'shift_model': emp_data.get('shift_model', 'alternating_FS'),
            'id': emp_data.get('id', ''),
            'weeks': []
        }
        schedule = emp_data.get('schedule', {})
        for week_key in sorted(schedule.keys()):
            week_data = schedule[week_key]
            try:
                year_str, w_str = week_key.split('-W')
                kw_str = f"KW{int(w_str)}_{year_str[-2:]}"
            except Exception:
                kw_str = week_key
            stored_days = week_data.get('days', {})
            # Build exception map from stored days (CSV imports only store exception days,
            # UI updates pre-fill all 5 days — both cases are handled uniformly here).
            exception_map = {}
            for date_str, day_data in stored_days.items():
                try:
                    d = date.fromisoformat(date_str)
                except Exception:
                    continue
                exception_map[d] = {
                    'ereignis': day_data.get('ereignis', ''),
                    'holiday': day_data.get('holiday', False)
                }
            # Always generate the full Mon–Fri skeleton and overlay stored exceptions.
            # This ensures normal working days are present even when only exception days
            # are stored (e.g. after a CSV import), so the People KPI counts correctly.
            days_list = []
            try:
                y_num, w_num = (int(p) for p in week_key.split('-W'))
                monday = date.fromisocalendar(y_num, w_num, 1)
                for j in range(5):
                    d = monday + timedelta(days=j)
                    d_str = d.isoformat()
                    stored = exception_map.get(d)
                    days_list.append({
                        'date': d,
                        'ereignis': stored['ereignis'] if stored else '',
                        'holiday': stored['holiday'] if stored else d_str in holidays_map
                    })
            except Exception:
                # Fallback: use only what was stored if week_key parsing fails
                for d, info in sorted(exception_map.items()):
                    days_list.append({'date': d, 'ereignis': info['ereignis'], 'holiday': info['holiday']})
            employee_dict['weeks'].append({
                'KW': kw_str,
                'Schicht': week_data.get('shift', ''),
                'days': days_list
            })
        # Auto-extend basierend auf shift_model + Referenzwoche für aktuelle + kommende 16 Wochen
        shift_model = employee_dict.get('shift_model', 'alternating_FS')
        ref_week_key = emp_data.get('shift_model_ref_week', '')
        ref_shift_val = emp_data.get('shift_model_ref_shift', '').upper()
        lib_pattern = lib_lookup[shift_model]['pattern'] if shift_model in lib_lookup else None
        # Custom pattern needs ref_week but not ref_shift; fixed models need ref_week + ref_shift
        has_ref = bool(ref_week_key and (lib_pattern or ref_shift_val or shift_model in ('fixed_F', 'fixed_S', 'fixed_N')))
        if shift_model != 'none' and has_ref and employee_dict['weeks']:
            sorted_weeks = sorted(employee_dict['weeks'], key=lambda w: w.get('KW', ''))
            weeks_with_days = [w for w in sorted_weeks if w.get('days')]
            if weeks_with_days:
                last_days = weeks_with_days[-1].get('days', [])
                if last_days:
                    last_date = max((d['date'] for d in last_days if isinstance(d.get('date'), date)), default=None)
                    if last_date:
                        try:
                            ry, rw = ref_week_key.split('-W')
                            ref_monday = date.fromisocalendar(int(ry), int(rw), 1)
                        except Exception:
                            ref_monday = None
                        if ref_monday:
                            existing_week_keys = {w['KW'] for w in employee_dict['weeks']}
                            holidays_map = get_holidays_set()
                            today = date.today()
                            target_end = today + timedelta(weeks=16)
                            next_monday = last_date - timedelta(days=last_date.weekday()) + timedelta(weeks=1)
                            while next_monday <= target_end:
                                iso_year, iso_week, _ = next_monday.isocalendar()
                                kw_str = f"KW{iso_week}_{str(iso_year)[-2:]}"
                                if kw_str not in existing_week_keys:
                                    delta = (next_monday - ref_monday).days // 7
                                    if lib_pattern:
                                        next_shift = lib_pattern[delta % len(lib_pattern)]
                                    elif shift_model == 'alternating_FS':
                                        rs = ref_shift_val if ref_shift_val in ('F', 'S') else 'F'
                                        next_shift = rs if delta % 2 == 0 else ('S' if rs == 'F' else 'F')
                                    elif shift_model == 'fixed_F':
                                        next_shift = 'F'
                                    elif shift_model == 'fixed_S':
                                        next_shift = 'S'
                                    elif shift_model == 'fixed_N':
                                        next_shift = 'N'
                                    else:
                                        next_monday += timedelta(weeks=1)
                                        continue
                                    days_list = []
                                    for j in range(5):
                                        d = next_monday + timedelta(days=j)
                                        d_str = d.isoformat()
                                        days_list.append({
                                            'date': d,
                                            'ereignis': '',
                                            'holiday': d_str in holidays_map
                                        })
                                    employee_dict['weeks'].append({
                                        'KW': kw_str,
                                        'Schicht': next_shift,
                                        'days': days_list
                                    })
                                    existing_week_keys.add(kw_str)
                                next_monday += timedelta(weeks=1)
        employees.append(employee_dict)
    ALL_EMPLOYEES = employees
    print(f"INFO: {len(ALL_EMPLOYEES)} Mitarbeiter aus Anwesenheits-JSON geladen.")
    return employees


def _convert_csv_weeks_to_schedule(weeks):
    """Convert old CSV weeks list to attendance JSON schedule dict."""
    schedule = {}
    for week in weeks:
        kw_str = week.get('KW', '')
        m = re.match(r'KW(\d{1,2})_(\d{2})', kw_str)
        if not m:
            continue
        week_num = int(m.group(1))
        year = 2000 + int(m.group(2))
        iso_key = f"{year}-W{week_num:02d}"
        days_dict = {}
        for day in week.get('days', []):
            d = day.get('date')
            if not d:
                continue
            date_str = d.isoformat() if hasattr(d, 'isoformat') else str(d)
            ereignis = day.get('ereignis', '')
            if ereignis:
                days_dict[date_str] = {'ereignis': ereignis}
        schedule[iso_key] = {
            'shift': week.get('Schicht', ''),
            'days': days_dict
        }
    return schedule


def merge_csv_employees_into_attendance_json(parsed_csv_employees):
    """Import CSV schedule weeks into attendance JSON for existing employees only.
    Only weeks present in the CSV are written (overwriting those weeks).
    No new employees are created and no master data (name, initials, group) is changed."""
    data = load_raw_attendance_data()

    existing_by_id = {}
    existing_by_name = {}
    for emp in data.get('employees', []):
        if emp.get('id'):
            existing_by_id[emp['id']] = emp
        if emp.get('normalized_name'):
            existing_by_name[emp['normalized_name']] = emp

    updated = 0
    skipped = 0
    for csv_emp in parsed_csv_employees:
        norm_name = csv_emp.get('normalized_name', '')
        emp_id = csv_emp.get('id', '')
        schedule = _convert_csv_weeks_to_schedule(csv_emp.get('weeks', []))

        existing = existing_by_id.get(emp_id) or existing_by_name.get(norm_name)

        if existing is None:
            skipped += 1
            continue

        if 'schedule' not in existing:
            existing['schedule'] = {}
        for week_key, week_data in schedule.items():
            existing['schedule'][week_key] = week_data
        updated += 1

    save_raw_attendance_data(data)
    print(f"INFO: CSV-Import: {updated} Mitarbeiter aktualisiert, {skipped} unbekannte übersprungen.")
    return data


def load_employees_from_csv(file_path, merkmale_in_order_param=None, parent=None):
    global ALL_EMPLOYEES, ALL_USERS

    if not os.path.exists(file_path):
        print(f"WARNUNG: Mitarbeiterdatei {file_path} nicht gefunden.")
        ALL_EMPLOYEES = []
        return []

    if not ALL_USERS:
        load_all_users_on_startup()

    user_map_by_name = {u.get('normalized_name'): u for u in ALL_USERS if u.get('normalized_name')}

    parsed_employees = []
    try:
        with open(file_path, mode='r', newline='', encoding='utf-8-sig') as file:
            reader = csv.reader(file)
            rows = list(reader)

            if len(rows) < 2:
                print(f"WARNUNG: Mitarbeiterdatei {file_path} enthält nicht genügend Zeilen.")
                ALL_EMPLOYEES = []
                return []

            header = rows[0]
            data_rows = rows[1:]

            name_idx = 1  # Spalte B in Excel ist Index 1

            header_info = []
            current_kw_str_base = None
            active_year_suffix = None
            fallback_year_for_dates = date.today().year

            for col_idx, header_cell_raw in enumerate(header):
                header_cell_cleaned = header_cell_raw.replace('\n', ' ').strip()
                if col_idx == 0: header_info.append({'type': 'quali_group', 'index': col_idx}); continue
                if col_idx == 1: header_info.append({'type': 'name', 'index': col_idx}); continue
                if col_idx == 2: header_info.append({'type': 'initials_col', 'index': col_idx}); continue
                year_in_current_date_cell_match = re.search(
                    r'(?:Jan|Feb|Mär|Mrz|Apr|Mai|Jun|Jul|Aug|Sep|Okt|Nov|Dez)\s*(\d{2})', header_cell_cleaned,
                    re.IGNORECASE)
                if year_in_current_date_cell_match: active_year_suffix = year_in_current_date_cell_match.group(1)
                kw_match = re.match(r'KW\s*(\d{1,2})(?:_(\d{2}))?', header_cell_cleaned, re.IGNORECASE)
                if kw_match:
                    kw_num_str = kw_match.group(1)
                    year_suffix_in_kw = kw_match.group(2)
                    current_kw_str_base = f"KW{kw_num_str}"
                    if year_suffix_in_kw: active_year_suffix = year_suffix_in_kw
                    full_kw_str_for_header_info = current_kw_str_base
                    if active_year_suffix: full_kw_str_for_header_info += f"_{active_year_suffix}"
                    header_info.append({'type': 'kw_shift_col', 'kw': full_kw_str_for_header_info, 'index': col_idx})
                else:
                    parsed_date_obj = parse_german_date_from_header(header_cell_raw, active_year_suffix,
                                                                    fallback_year_for_dates)
                    if parsed_date_obj and current_kw_str_base:
                        full_kw_str_for_date_context = current_kw_str_base
                        if active_year_suffix: full_kw_str_for_date_context += f"_{active_year_suffix}"
                        header_info.append({'type': 'day_event_col', 'kw_context': full_kw_str_for_date_context,
                                            'date': parsed_date_obj, 'index': col_idx,
                                            'is_holiday_header': "feier" in header_cell_cleaned.lower()})
                    else:
                        header_info.append({'type': 'unknown', 'text': header_cell_cleaned, 'index': col_idx})

            for emp_row_idx, emp_data_list in enumerate(data_rows):
                if not any(d.strip() for d in emp_data_list): continue

                quali_val = emp_data_list[0].strip()
                name_val = emp_data_list[name_idx].strip()
                initials_val = emp_data_list[2].strip()

                name_parts = [p.strip().lower() for p in name_val.split(',')]
                normalized_name_emp = ", ".join(name_parts) if len(name_parts) == 2 else name_val.lower()

                if not initials_val and name_val:
                    parts = name_val.split(',')
                    if len(parts) > 1:
                        initials_val = (parts[1].strip()[0] + parts[0].strip()[0]).upper()
                    elif len(name_val.split()) > 1:
                        name_parts = name_val.split();
                        initials_val = (name_parts[0][0] + name_parts[-1][0]).upper()

                user_obj = user_map_by_name.get(normalized_name_emp)
                employee_id = user_obj.get('id', '') if user_obj else ''
                if not employee_id:
                    print(
                        f"WARNUNG (employees.csv): Konnte keine eindeutige ID für '{name_val}' finden. Mitarbeiter wird evtl. nicht korrekt verarbeitet.")

                employee_dict = {
                    'name': name_val,
                    'initials': initials_val,
                    'quali': quali_val,
                    'normalized_name': normalized_name_emp,
                    'id': employee_id,  # <-- KORREKTUR (kommt bereits klein an)
                    'weeks': []
                }
                current_week_details_for_emp = None

                for col_info in header_info:
                    if col_info['type'] in ['quali_group', 'name', 'initials_col', 'unknown'] or col_info[
                        'index'] >= len(emp_data_list): continue
                    cell_value = emp_data_list[col_info['index']].strip()
                    if col_info['type'] == 'kw_shift_col':
                        if current_week_details_for_emp: employee_dict['weeks'].append(current_week_details_for_emp)
                        current_week_details_for_emp = {'KW': col_info['kw'], 'Schicht': cell_value, 'days': []}
                    elif col_info['type'] == 'day_event_col' and current_week_details_for_emp:
                        if current_week_details_for_emp['KW'] == col_info['kw_context']:
                            current_week_details_for_emp['days'].append(
                                {'date': col_info['date'], 'ereignis': cell_value,
                                 'holiday': col_info.get('is_holiday_header', False)})
                if current_week_details_for_emp: employee_dict['weeks'].append(current_week_details_for_emp)

                parsed_employees.append(employee_dict)

        ALL_EMPLOYEES = parsed_employees
        print(
            f"INFO: {len(ALL_EMPLOYEES)} Mitarbeiter-Anwesenheitsdaten aus {file_path} geladen und mit IDs angereichert.")
        return parsed_employees

    except FileNotFoundError:
        print(f"FEHLER: Mitarbeiterdatei {file_path} nicht gefunden.")
        ALL_EMPLOYEES = []
        return []
    except Exception as e:
        print(f"FEHLER beim Laden und Verarbeiten der Mitarbeiter-Anwesenheitsdatei: {e}")
        traceback.print_exc()
        ALL_EMPLOYEES = []
        return []


def save_qualifications_to_csv(file_path, qualifications_data, merkmale_order, parent=None, silent=False):
    try:
        header = ['Quali', 'Name', 'ID']
        for merkmal_name in merkmale_order:
            header.append(merkmal_name)  # Hauptspalte für Status
            header.extend([
                f'{merkmal_name}_Start',
                f'{merkmal_name}_End',
                f'{merkmal_name}_Cert'
            ])

        with open(file_path, mode='w', newline='', encoding='utf-8-sig') as file:
            writer = csv.DictWriter(file, fieldnames=header)
            writer.writeheader()

            for qual_entry in qualifications_data:
                row_to_write = {
                    'Quali': qual_entry.get('quali', ''),
                    'Name': qual_entry.get('name', ''),
                    'ID': qual_entry.get('id', '')
                }
                merkmale_werte_map = {m.get('merkmal'): m for m in qual_entry.get('merkmale', [])}
                for merkmal_name in merkmale_order:
                    merkmal_data = merkmale_werte_map.get(merkmal_name, {})
                    row_to_write[merkmal_name] = merkmal_data.get('wert', '')
                    row_to_write[f'{merkmal_name}_Start'] = merkmal_data.get('start_date', '')
                    row_to_write[f'{merkmal_name}_End'] = merkmal_data.get('end_date', '')
                    row_to_write[f'{merkmal_name}_Cert'] = merkmal_data.get('cert_path', '')

                writer.writerow(row_to_write)

        if not silent:
            print(f"INFO: Qualifikationen erfolgreich in {file_path} im neuen Format gespeichert.")
        return True
    except Exception as e:
        print(f"FEHLER beim Speichern der Qualifikationen im neuen Format: {e}")
        traceback.print_exc()
        return False


def synchronize_qualifications_with_employees_logic(qualifications_list, employees_list, merkmale_order_list):
    if not employees_list:
        return False

    valid_merkmale_order = merkmale_order_list if isinstance(merkmale_order_list, list) else []
    if not valid_merkmale_order:
        print(
            "WARNUNG (Sync): merkmale_order_list ist leer oder ungültig. Neue Quali-Einträge werden ohne spezifische Merkmale erstellt.")

    quali_map_by_id = {q.get('id', '').strip().lower(): q for q in qualifications_list if q.get('id')}
    user_map_by_name = {u.get('normalized_name'): u for u in ALL_USERS if u.get('normalized_name')}

    new_qualifications_added_flag = False
    for emp in employees_list:
        emp_normalized_name = emp.get('normalized_name')
        if not emp_normalized_name:
            continue

        user_obj = user_map_by_name.get(emp_normalized_name)
        if not user_obj:
            print(
                f"WARNUNG (Sync): Mitarbeiter '{emp.get('name')}' aus Schichtplan nicht in Benutzerliste gefunden. Überspringe.")
            continue

        emp_id = user_obj.get('id', '').strip().lower()
        if not emp_id:
            print(f"WARNUNG (Sync): Benutzer '{emp.get('name')}' hat keine ID in user_display.csv. Überspringe.")
            continue

        if emp_id not in quali_map_by_id:
            print(
                f"INFO (Sync): Neuer Mitarbeiter '{emp.get('name')}' mit ID '{emp_id}' ohne Quali-Eintrag. Erstelle Standardeintrag.")

            new_qual_name = f"{user_obj.get('nachname').upper()}, {user_obj.get('vorname').capitalize()}"

            new_qual_entry = {
                'quali': emp.get('quali', '').strip(),
                'name': new_qual_name,
                'id': emp_id,
                'merkmale': []
            }
            for merkmal_name_iter in valid_merkmale_order:
                new_qual_entry['merkmale'].append({
                    'merkmal': merkmal_name_iter,
                    'wert': '',
                    'start_date': '',
                    'end_date': '',
                    'cert_path': ''
                })

            qualifications_list.append(new_qual_entry)
            quali_map_by_id[emp_id] = new_qual_entry
            new_qualifications_added_flag = True

    return new_qualifications_added_flag


def load_and_prepare_qualifications_data():
    global ALL_QUALIFICATIONS, ALL_EMPLOYEES, MERKMALE_IN_ORDER

    if not ALL_USERS:
        load_all_users_on_startup()

    loaded_qualis, loaded_merkmale = load_qualifications_from_csv(QUALIFICATIONS_FILE_PATH)
    ALL_QUALIFICATIONS = loaded_qualis
    if os.path.exists(ATTENDANCE_DATA_FILE):
        load_employees_from_attendance_json()
    else:
        ALL_EMPLOYEES = load_employees_from_csv(EMPLOYEES_FILE_PATH)
        if ALL_EMPLOYEES:
            merge_csv_employees_into_attendance_json(ALL_EMPLOYEES)

    if loaded_merkmale:
        MERKMALE_IN_ORDER = loaded_merkmale
    elif not MERKMALE_IN_ORDER:
        MERKMALE_IN_ORDER = ["CFM56-7B", "PW1500G", "LEAP-1A", "Training Allg."]  # Notfall-Fallback
        print(f"WARNUNG: MERKMALE_IN_ORDER ist leer, verwende Default: {MERKMALE_IN_ORDER}")

    made_changes = synchronize_qualifications_with_employees_logic(ALL_QUALIFICATIONS, ALL_EMPLOYEES, MERKMALE_IN_ORDER)
    if made_changes:
        save_qualifications_to_csv(QUALIFICATIONS_FILE_PATH, ALL_QUALIFICATIONS, MERKMALE_IN_ORDER, silent=True)
        ALL_QUALIFICATIONS, reloaded_merkmale = load_qualifications_from_csv(QUALIFICATIONS_FILE_PATH)
        if reloaded_merkmale:
            MERKMALE_IN_ORDER = reloaded_merkmale

    # Nur Einträge aktiver Mitarbeiter anzeigen — IS_EMPLOYEE=False und gelöschte User bleiben in der CSV archiviert
    active_ids = {u['id'].lower() for u in ALL_USERS if u.get('is_employee', True)}
    ALL_QUALIFICATIONS = [q for q in ALL_QUALIFICATIONS if q.get('id', '').lower() in active_ids]

    ALL_QUALIFICATIONS.sort(key=lambda q: (
        str(q.get('quali', 'zzzz')).lower(),
        str(q.get('name', '')).lower()
    ))

    grouped_qualifications = {}
    for qual_entry in ALL_QUALIFICATIONS:
        group_key = qual_entry.get('quali', 'Sonstige')
        if not group_key.strip(): group_key = 'Sonstige'
        if group_key not in grouped_qualifications:
            grouped_qualifications[group_key] = []
        grouped_qualifications[group_key].append(qual_entry)

    return grouped_qualifications, MERKMALE_IN_ORDER


# 3. Decorators und Hilfsfunktionen
def load_all_users_on_startup():
    global ALL_USERS
    try:
        if os.path.exists(USERS_FILE_PATH):
            with open(USERS_FILE_PATH, mode='r', newline='', encoding='utf-8-sig') as file:
                # Wichtig: fieldnames explizit übergeben, falls alte Spalten noch da sind
                reader = csv.DictReader(file)
                temp_users = []
                for row in reader:
                    nachname_norm = row.get('NACHNAME', '').strip().lower()
                    vorname_norm = row.get('VORNAME', '').strip().lower()
                    normalized_name_key = f"{nachname_norm}, {vorname_norm}"

                    # Neue Logik: Rollen aus der ROLES-Spalte lesen
                    roles_str = row.get('ROLES', '')
                    roles_list = [r.strip().upper() for r in roles_str.split(',') if r.strip()]

                    # Fallback-Logik für Abwärtskompatibilität, falls ROLES leer ist
                    is_admin_from_col = row.get('ADMIN', '').strip().lower() == 'x'
                    if Roles.ADMIN not in roles_list and is_admin_from_col:
                        roles_list.append(Roles.ADMIN)

                    if not roles_list:  # Wenn immer noch leer, gib minimalen Observer-Status
                        roles_list.append(Roles.OBSERVER)

                    units_str = row.get('UNITS', '')
                    units_list = [u.strip() for u in units_str.split(',') if u.strip()]

                    # IS_EMPLOYEE: default 'x' wenn Spalte fehlt (Abwärtskompatibilität)
                    is_employee = row.get('IS_EMPLOYEE', 'x').strip().lower() == 'x'

                    user = {
                        'nachname': row.get('NACHNAME', '').strip(),
                        'vorname': row.get('VORNAME', '').strip(),
                        'id': row.get('ID', '').strip().lower(),
                        'normalized_name': normalized_name_key,
                        'level': row.get('LEVEL', 'Standardbenutzer').strip(),
                        'password': row.get('PASSWORD', '').strip(),
                        'roles': sorted(list(set(roles_list))),
                        'units': units_list,
                        'is_employee': is_employee,
                    }
                    temp_users.append(user)
                ALL_USERS = temp_users
                print(f"INFO: {len(ALL_USERS)} Benutzer (mit Rollen) aus {USERS_FILE_PATH} geladen.")
        else:
            print(f"WARNUNG: {USERS_FILE_PATH} nicht gefunden. Erstelle leere Benutzerliste.")
            ALL_USERS = []
    except Exception as e:
        print(f"FEHLER beim Laden der Benutzer: {e}")
        traceback.print_exc()
        ALL_USERS = []


def _save_users_csv():
    """Schreibt ALL_USERS in USERS_FILE_PATH. Einheitliche Stelle für alle Speicher-Operationen."""
    rows = []
    for u in ALL_USERS:
        is_admin = Roles.ADMIN in u.get('roles', [])
        rows.append({
            'NACHNAME': u['nachname'],
            'VORNAME': u['vorname'],
            'ID': u['id'],
            'LEVEL': u.get('level', ''),
            'PASSWORD': u.get('password', ''),
            'ADMIN': 'x' if is_admin else '',
            'ROLES': ",".join(u.get('roles', [])),
            'UNITS': ",".join(u.get('units', [])),
            'IS_EMPLOYEE': 'x' if u.get('is_employee', True) else '',
        })
    with open(USERS_FILE_PATH, mode='w', newline='', encoding='utf-8-sig') as f:
        writer = csv.DictWriter(f, fieldnames=USER_CSV_FIELDNAMES)
        writer.writeheader()
        writer.writerows(rows)


def _provision_employee(user_obj):
    """Erstellt Attendance-Slot und leeren Quali-Eintrag für einen neuen IS_EMPLOYEE-User."""
    uid = user_obj['id']
    name = f"{user_obj['nachname']}, {user_obj['vorname'].capitalize()}"
    initials = (user_obj['vorname'][0] + user_obj['nachname'][0]).upper() if user_obj['vorname'] and user_obj['nachname'] else uid.upper()[:2]
    # Attendance-Slot anlegen (nur wenn noch nicht vorhanden)
    att_data = load_raw_attendance_data()
    if not any(e.get('id') == uid for e in att_data.get('employees', [])):
        norm_name = f"{user_obj['nachname'].strip().lower()}, {user_obj['vorname'].strip().lower()}"
        att_data.setdefault('employees', []).append({
            'id': uid, 'name': name, 'initials': initials,
            'normalized_name': norm_name, 'group_id': '', 'schedule': {}
        })
        save_raw_attendance_data(att_data)
        load_employees_from_attendance_json()
        print(f"INFO (_provision): Attendance-Slot für '{name}' ({uid}) angelegt.")

    # Leeren Quali-Eintrag anlegen (nur wenn noch nicht vorhanden)
    global ALL_QUALIFICATIONS, MERKMALE_IN_ORDER
    if not any(q.get('id', '').lower() == uid for q in ALL_QUALIFICATIONS):
        new_qual = {
            'quali': '',
            'name': f"{user_obj['nachname'].upper()}, {user_obj['vorname'].capitalize()}",
            'id': uid,
            'merkmale': [{'merkmal': m, 'wert': '', 'start_date': '', 'end_date': '', 'cert_path': ''} for m in MERKMALE_IN_ORDER]
        }
        ALL_QUALIFICATIONS.append(new_qual)
        save_qualifications_to_csv(QUALIFICATIONS_FILE_PATH, ALL_QUALIFICATIONS, MERKMALE_IN_ORDER, silent=True)
        print(f"INFO (_provision): Quali-Eintrag für '{name}' ({uid}) angelegt.")


def _deprovision_employee(uid):
    """Entfernt Attendance-Slot wenn IS_EMPLOYEE deaktiviert wird. Quali bleibt (historische Daten)."""
    att_data = load_raw_attendance_data()
    before = len(att_data.get('employees', []))
    att_data['employees'] = [e for e in att_data.get('employees', []) if e.get('id') != uid]
    if len(att_data['employees']) < before:
        save_raw_attendance_data(att_data)
        load_employees_from_attendance_json()
        print(f"INFO (_deprovision): Attendance-Slot für '{uid}' entfernt.")


def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return redirect(url_for('login_page', next=request.url))
        return f(*args, **kwargs)

    return decorated_function


def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return redirect(url_for('login_page', next=request.url))
        if not session.get('is_admin'):
            if request.path.startswith('/api/'):
                return jsonify({"error": "Keine Berechtigung für diese Aktion."}), 403
            return redirect(url_for('dashboard_page'))
        return f(*args, **kwargs)

    return decorated_function


def get_monday_of_current_week():
    today = datetime.now()
    return (today - timedelta(days=today.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)


def get_project_status_details(project_name, all_projects_data_for_status):
    project_data = None
    for p in all_projects_data_for_status:
        # Der Projektname wird hier bereits geprüft, aber zur Sicherheit
        if p.get('project_name') == project_name and p.get('project_typ') == "ENGINE":
            project_data = p
            break

    if not project_data:
        return None

    project_tasks_for_status = project_data.get('tasks', [])
    due_date = None

    if project_tasks_for_status:
        project_tasks_for_status.sort(key=lambda t: datetime.fromisoformat(t['end_datetime_iso']))
        last_task_end_dt = datetime.fromisoformat(project_tasks_for_status[-1]['end_datetime_iso'])
        due_date = last_task_end_dt.date()

    due_date_display = due_date.strftime("%d. %b %Y") if due_date else "N/A"

    current_step_index, last_task_name_for_step = -1, ""
    if project_tasks_for_status:
        sorted_tasks_by_start_desc = sorted(project_tasks_for_status,
                                            key=lambda t: datetime.fromisoformat(t['start_datetime_iso']), reverse=True)
        for task in sorted_tasks_by_start_desc:
            task_name_lower = task['task_name'].lower().strip()
            mapped_step_for_task = next(
                (step_name for keyword, step_name in TASK_TO_STEP_MAPPING.items() if keyword in task_name_lower), None)
            if mapped_step_for_task and mapped_step_for_task in DEFAULT_ENGINE_PROJECT_STEPS:
                step_idx_in_default = DEFAULT_ENGINE_PROJECT_STEPS.index(mapped_step_for_task)
                if step_idx_in_default > current_step_index:
                    current_step_index = step_idx_in_default
                    last_task_name_for_step = task['task_name']

    if project_tasks_for_status and current_step_index == -1:
        current_step_index = 0

    steps_with_status, current_step_for_notes_obj = [], None
    main_step_id_counter = 1
    for i, step_name in enumerate(DEFAULT_ENGINE_PROJECT_STEPS):
        status, status_detail = ("Upcoming", "")
        if current_step_index == -1 and not project_tasks_for_status:
            status = "Upcoming"
        elif i < current_step_index:
            status = "Completed"
        elif i == current_step_index:
            status = "In Progress"
            status_detail = f"Task: {last_task_name_for_step}" if last_task_name_for_step else ""
        else:
            status = "Upcoming"

        if status == "In Progress": current_step_for_notes_obj = {"name": step_name,
                                                                  "id_str": str(main_step_id_counter)}

        last_updated_by_demo, last_updated_at_demo = None, None
        if status == "Completed":
            last_updated_by_demo = "System"
            last_updated_at_demo = (
                    datetime.now() - timedelta(days=len(DEFAULT_ENGINE_PROJECT_STEPS) - i, hours=i * 2)).strftime(
                '%d. %b %Y, %H:%M')
        elif status == "In Progress":
            last_updated_by_demo = LOGGED_IN_USER_DISPLAY_NAME_FOR_PYTHON
            last_updated_at_demo = (datetime.now() - timedelta(hours=i)).strftime('%d. %b %Y, %H:%M')

        steps_with_status.append({
            "id_str": str(main_step_id_counter),
            "parent_id_str": None,
            "type": "main",
            "is_expanded": True,
            "name": step_name, "status": status, "status_detail": status_detail,
            "last_updated_by": last_updated_by_demo, "last_updated_at": last_updated_at_demo,
            "history": []
        })
        main_step_id_counter += 1

    progress_percent = 0
    total_steps = len(DEFAULT_ENGINE_PROJECT_STEPS)
    if total_steps > 0:
        if current_step_index == -1 and not project_tasks_for_status:
            progress_percent = 0
        elif current_step_index == total_steps - 1 and steps_with_status[current_step_index]['status'] == "Completed":
            progress_percent = 100
        elif current_step_index == total_steps - 1 and steps_with_status[current_step_index]['status'] == "In Progress":
            progress_percent = 99
        elif current_step_index >= 0:
            completed_main_steps = current_step_index
            if steps_with_status[current_step_index]['status'] == "In Progress":
                progress_percent = int(((completed_main_steps + 0.5) / total_steps) * 100)
            elif steps_with_status[current_step_index]['status'] == "Completed":
                progress_percent = int(((completed_main_steps + 1) / total_steps) * 100)
            else:
                progress_percent = int((completed_main_steps / total_steps) * 100)
            progress_percent = min(progress_percent, 99)

    return {
        "name": project_name, "engine_type": project_data.get("engine_type", "N/A"),
        "engine_serial": project_data.get("engine_serial", "N/A"),
        "due_date_display": due_date_display, "progress_percent": progress_percent,
        "steps": steps_with_status, "current_step_for_notes": current_step_for_notes_obj
    }


def get_current_username_from_session(): return session.get('username_display', "Gast")


def get_current_user_full_name_from_session():
    user_id = session.get('user_id')
    if user_id:
        user_data = next((user for user in ALL_USERS if user['id'] == user_id), None)
        if user_data:
            return f"{user_data.get('vorname', '')} {user_data.get('nachname', '')}".strip()
    return get_current_username_from_session()


DEFAULT_PAST_WEEKS_PRESENCE = 0  # Standardmäßig keine Wochen in der Vergangenheit initial anzeigen
DEFAULT_FUTURE_WEEKS_PRESENCE = 1  # Standardmäßig die aktuelle + 1 nächste Woche (also 2 Wochen total)


def get_employee_presence_data_for_web(employees_list, current_week_offset=0, num_weeks_to_display=2):
    """
    Bereitet Anwesenheitsdaten für die Web-Ansicht auf.
    current_week_offset: 0 für aktuelle Woche, 1 für nächste Woche, -1 für vorherige usw.
    num_weeks_to_display: Wie viele Wochen ab der offset_week angezeigt werden sollen (z.B. 2 für offset_week und offset_week+1)
    """
    if not employees_list:
        return {"headers": [], "employee_groups": [], "last_loaded": "N/A", "current_week_offset": current_week_offset,
                "total_weeks_in_data": 0}

    employees_list.sort(key=lambda e: (str(e.get('quali', '')).lower(), str(e.get('name', '')).lower()))
    grouped_employees_by_quali = {}
    for emp in employees_list:
        quali_key = emp.get('quali', 'Unbekannt')
        if not quali_key.strip(): quali_key = 'Unbekannt'
        if quali_key not in grouped_employees_by_quali:
            grouped_employees_by_quali[quali_key] = []
        grouped_employees_by_quali[quali_key].append(emp)

    today = date.today()
    # Montag der aktuellen, realen Woche
    start_of_actual_current_week = today - timedelta(days=today.weekday())

    # Startdatum der *ersten anzuzeigenden Woche* basierend auf dem Offset
    display_period_start_week_monday = start_of_actual_current_week + timedelta(weeks=current_week_offset)
    # Enddatum der *letzten anzuzeigenden Woche*
    display_period_end_date = display_period_start_week_monday + timedelta(weeks=num_weeks_to_display) - timedelta(
        days=1)

    all_dates_in_period = []
    current_d = display_period_start_week_monday
    while current_d <= display_period_end_date:
        all_dates_in_period.append(current_d)
        current_d += timedelta(days=1)

    # Finde die Grenzen der gesamten verfügbaren Daten in employees.csv, um Navigationslimits zu setzen
    min_date_in_csv = None
    max_date_in_csv = None
    if employees_list:
        for emp in employees_list:
            for week_data in emp.get('weeks', []):
                for day_data in week_data.get('days', []):
                    d = day_data.get('date')
                    if isinstance(d, date):
                        if min_date_in_csv is None or d < min_date_in_csv:
                            min_date_in_csv = d
                        if max_date_in_csv is None or d > max_date_in_csv:
                            max_date_in_csv = d

    total_weeks_in_data = 0
    if min_date_in_csv and max_date_in_csv:
        # Montag der ersten Woche in CSV
        min_week_start_csv = min_date_in_csv - timedelta(days=min_date_in_csv.weekday())
        # Montag der letzten Woche in CSV
        max_week_start_csv = max_date_in_csv - timedelta(days=max_date_in_csv.weekday())
        if max_week_start_csv >= min_week_start_csv:
            total_weeks_in_data = ((max_week_start_csv - min_week_start_csv).days // 7) + 1

    date_to_kw_map = {}
    for d_obj in all_dates_in_period:  # Nur die Daten für den anzuzeigenden Zeitraum
        year, week_num, _ = d_obj.isocalendar()
        date_to_kw_map.setdefault(f"KW{week_num}_{str(year)[-2:]}", []).append(d_obj)

    def parse_kw_year_key(kw_year_str):
        match = re.match(r"KW(\d+)_(\d+)", kw_year_str)
        if match: return int("20" + match.group(2)), int(match.group(1))
        return 0, 0

    sorted_kw_year_keys = sorted(date_to_kw_map.keys(), key=parse_kw_year_key)

    web_headers = []
    date_meta_info = {d.isoformat(): {"is_today": d == today, "is_holiday": False} for d in all_dates_in_period}

    for kw_year_key_str in sorted_kw_year_keys:
        web_headers.append({"type": "kw", "text": kw_year_key_str})
        dates_in_this_week = sorted(date_to_kw_map[kw_year_key_str])
        for d_obj in dates_in_this_week:
            web_headers.append({
                "type": "day", "text": d_obj.strftime('%d.%m'), "date_iso": d_obj.isoformat(),
                "is_today": date_meta_info[d_obj.isoformat()]["is_today"],
                "is_holiday": date_meta_info[d_obj.isoformat()]["is_holiday"],
                "is_weekend": d_obj.weekday() >= 5
            })

    employee_groups_for_web = []
    for quali_name, emps_in_group in grouped_employees_by_quali.items():
        group_data = {"group_name": quali_name, "employees": []}
        for emp in emps_in_group:
            emp_web_data = {"name": emp.get('name', ''), "initials": emp.get('initials', ''), "presence": []}
            emp_presence_map_by_date = {}
            emp_shift_map_by_kw_str = {}

            for week_data_csv in emp.get('weeks', []):
                kw_str_from_emp = week_data_csv.get('KW')
                shift_from_emp = week_data_csv.get('Schicht')
                if kw_str_from_emp and shift_from_emp:
                    emp_shift_map_by_kw_str[kw_str_from_emp] = shift_from_emp
                for day_data_csv in week_data_csv.get('days', []):
                    if isinstance(day_data_csv.get('date'), date):
                        date_obj_csv = day_data_csv['date']
                        emp_presence_map_by_date[date_obj_csv.isoformat()] = {
                            'ereignis': day_data_csv.get('ereignis', ''),
                            'holiday': day_data_csv.get('holiday', False)
                        }

            for header_item in web_headers:  # Iteriere nur über die Header des aktuellen Anzeigezeitraums
                if header_item["type"] == "kw":
                    shift_for_kw = emp_shift_map_by_kw_str.get(header_item["text"], '')
                    emp_web_data["presence"].append({"type": "kw", "shift": shift_for_kw})
                elif header_item["type"] == "day":
                    day_iso_str = header_item["date_iso"]
                    presence_info_for_day = emp_presence_map_by_date.get(day_iso_str,
                                                                         {"ereignis": "", "holiday": False})
                    emp_web_data["presence"].append({
                        "type": "day", "event": presence_info_for_day["ereignis"],
                        "is_holiday_from_data": presence_info_for_day["holiday"],
                    })
            group_data["employees"].append(emp_web_data)
        employee_groups_for_web.append(group_data)

    last_loaded_str = "N/A"
    if os.path.exists(EMPLOYEES_FILE_PATH):
        try:
            mtime = os.path.getmtime(EMPLOYEES_FILE_PATH)
            last_loaded_str = datetime.fromtimestamp(mtime).strftime("%d.%m.%Y %H:%M:%S")
        except:
            pass

    return {
        "headers": web_headers,
        "employee_groups": employee_groups_for_web,
        "last_loaded": last_loaded_str,
        "current_week_offset": current_week_offset,  # Wichtig für JS
        "num_weeks_displayed": num_weeks_to_display,  # Wichtig für JS
        "min_date_in_csv_iso": min_date_in_csv.isoformat() if min_date_in_csv else None,
        "max_date_in_csv_iso": max_date_in_csv.isoformat() if max_date_in_csv else None,
        "total_weeks_in_data": total_weeks_in_data
    }


def get_datetime_from_date_shift(date_obj: Optional[date], shift_str: str) -> Optional[datetime]:
    if not isinstance(date_obj, date):
        return None

    hour_val = TIME_MAPPING.get(shift_str.upper(), 0)  # Nimm 0 (AM) als Fallback

    try:
        # Hier ist der Trick: Erstelle ein naives datetime-Objekt
        return datetime(date_obj.year, date_obj.month, date_obj.day, hour_val)
    except Exception as e:
        print(f"Fehler in get_datetime_from_date_shift: {e}")
        return None


def split_datetime_to_date_shift(dt_obj):
    date_part = dt_obj.date()
    shift_part = 'AM' if dt_obj.hour < 12 else 'PM'
    return date_part, shift_part


def get_event_time_slots_for_api(event_data):
    slots = []
    if not event_data or 'start_datetime' not in event_data or 'end_datetime' not in event_data:
        return slots

    try:
        start_dt = datetime.fromisoformat(event_data['start_datetime'])
        end_dt = datetime.fromisoformat(event_data['end_datetime'])
    except (ValueError, TypeError):
        return slots

    current_dt = start_dt
    while current_dt < end_dt:
        date_part = current_dt.date()
        shift_part = 'AM' if current_dt.hour < 12 else 'PM'
        slots.append(f"{date_part.isoformat()}_{shift_part}")
        current_dt += timedelta(hours=12)
    return slots


def filter_sap_data(source_file_or_path, filter_rules: Optional[Dict] = None) -> Optional[pd.DataFrame]:
    try:
        import pandas as pd
    except ImportError:
        print("FEHLER: 'pandas' Paket wird benötigt. Bitte installieren: pip install pandas")
        return None

    try:
        df = pd.read_csv(source_file_or_path, sep=';', encoding='utf-8', dayfirst=True, low_memory=False)
    except Exception as e:
        print(f"FEHLER: SAP-CSV-Datei konnte nicht gelesen werden: {e}")
        return None

    required_columns = ["Project", "Engine Serial", "Customer", "Engine Type", "Workscope", "B3", "TS", "TE", "SA",
                        "Milestone type"]
    missing_cols = [col for col in required_columns if col not in df.columns]
    if missing_cols:
        print(f"FEHLER in filter_sap_data: Folgende Spalten fehlen in der SAP-Datei: {', '.join(missing_cols)}")
        return None

    # Grundfilterung (Datum, Milestone, etc.) - bleibt gleich
    df['TS_parsed'] = pd.to_datetime(df['TS'], format='%d.%m.%Y %H:%M:%S', errors='coerce')
    today = datetime.now()
    start_date, end_date = today - timedelta(weeks=4), today + timedelta(weeks=6)
    df = df.dropna(subset=['TS_parsed'])
    df = df.loc[(df['TS_parsed'] >= start_date) & (df['TS_parsed'] <= end_date)].copy()
    df = df[df['Milestone type'].astype(str) == "Plan (Planung, ZMMP)"].copy()

    # NEU: Verbesserte dynamische Filterungasdfsadfas dasdf sdfa
    if filter_rules:
        for column, patterns in filter_rules.items():
            if column in df.columns:
                try:
                    # Kombiniere alle Regex-Patterns mit einem ODER (|)
                    # case=False macht den Filter unempfindlich für Groß-/Kleinschreibung
                    combined_pattern = '|'.join(patterns)
                    df = df[df[column].astype(str).str.contains(combined_pattern, regex=True, na=False, case=False)]
                except Exception as e:
                    print(f"WARNUNG: Fehler beim Anwenden des SAP-Filters für Spalte '{column}': {e}")
                    # Im Fehlerfall wird diese Filterregel einfach übersprungen, anstatt die App abstürzen zu lassen.
                    pass

    if df.empty:
        print("INFO (SAP-Filter): Nach Anwendung aller Filter wurden keine passenden Projekte gefunden.")

    df.sort_values(by='TS_parsed', inplace=True)
    return df


def merge_into_timeline_projects(timeline_csv_path: str, filtered_df: pd.DataFrame,
                                 current_project_order: List[Tuple[str, str]]) -> bool:
    changes_made = False
    existing_projects = {}

    # 1. Bestehende Projekte laden
    if os.path.exists(timeline_csv_path):
        with open(timeline_csv_path, newline='', encoding='utf-8-sig') as f:
            reader = csv.reader(f)
            for row in reader:
                if not row: continue
                # Key ist (Project Code, Engine Serial)
                key = (row[2].strip(), row[3].strip())
                existing_projects[key] = row

    # 2. Prozess-Templates laden (für die dynamische Event-Generierung)
    process_templates = load_process_templates()

    # Hilfsfunktion für Formatierung
    def convert_to_original_format(value) -> str:
        if pd.isnull(value): return ""
        if isinstance(value, pd.Timestamp) or isinstance(value, datetime):
            return value.strftime("%d.%m.%Y %H:%M:%S")
        return str(value).strip()

    # 3. Durch die gefilterten SAP-Daten iterieren
    for _, row in filtered_df.iterrows():
        engine_serial = str(row["Engine Serial"]).strip()
        engine_type = str(row["Engine Type"]).strip()  # Wichtig für Template-Wahl
        project_code = str(row["Project"]).strip()
        customer = str(row["Customer"]).strip()
        project_name = f"{engine_type} - {project_code}"

        b3_str = convert_to_original_format(row['B3'])
        ts_str = convert_to_original_format(row['TS'])
        te_str = convert_to_original_format(row['TE'])
        sa_str = convert_to_original_format(row['SA'])

        if not ts_str: continue

        key = (project_code, engine_serial)

        # A) Projekt existiert bereits -> Prüfen auf Updates
        if key in existing_projects:
            existing_row = existing_projects[key]

            # Prüfen ob sich Metadaten geändert haben
            if (existing_row[5] != b3_str or existing_row[6] != ts_str or
                    existing_row[7] != te_str or existing_row[8] != sa_str or
                    existing_row[4] != customer or existing_row[0] != project_name):

                existing_row[0] = project_name
                existing_row[4] = customer
                existing_row[5] = b3_str
                existing_row[6] = ts_str
                existing_row[7] = te_str
                existing_row[8] = sa_str

                is_active = existing_row[9] == '1'

                # Nur wenn das Projekt INAKTIV ist, werden die Events neu generiert
                if not is_active:
                    try:
                        ts_datetime = datetime.strptime(ts_str, "%d.%m.%Y %H:%M:%S")
                    except ValueError:
                        continue

                        # Template laden
                    template = find_best_template_for_engine(engine_type, process_templates)
                    template_events = template.get("timeline_events", [])

                    dynamic_events = []
                    current_date = ts_datetime.date()
                    current_shift = 'AM' if ts_datetime.hour < 14 else 'PM'

                    # Events generieren
                    for event_def in template_events:
                        duration = event_def.get("duration_shifts", 1)
                        start_date_loop = current_date
                        start_shift_loop = current_shift

                        end_date_loop = start_date_loop
                        end_shift_loop = start_shift_loop

                        # KORREKTUR: Schleife läuft exakt 'duration' mal
                        for _ in range(duration):
                            if end_shift_loop == 'AM':
                                end_shift_loop = 'PM'
                            else:
                                end_shift_loop = 'AM'
                                end_date_loop += timedelta(days=1)

                        dynamic_events.append({
                            'title': event_def.get("name", "Unknown"),
                            'start': start_date_loop.strftime("%Y-%m-%d"),
                            'start_shift': start_shift_loop,
                            'end': end_date_loop.strftime("%Y-%m-%d"),
                            'end_shift': end_shift_loop,
                            'color': event_def.get("color", "grey")
                        })

                        # KORREKTUR: Nächstes Event startet direkt am Ende des aktuellen (keine Lücke)
                        current_date = end_date_loop
                        current_shift = end_shift_loop

                    # Zeile neu zusammenbauen: Metadaten + neue Events
                    updated_row = existing_row[:10]
                    for event in dynamic_events:
                        updated_row.extend([
                            event['title'], event['start'], event['start_shift'],
                            event['end'], event['end_shift'], event['color']
                        ])
                    existing_projects[key] = updated_row

                changes_made = True

        # B) Neues Projekt -> Anlegen
        else:
            new_row = [
                project_name, engine_type, project_code, engine_serial, customer,
                b3_str, ts_str, te_str, sa_str, '0'  # Standardmäßig inaktiv
            ]

            try:
                ts_datetime = datetime.strptime(ts_str, "%d.%m.%Y %H:%M:%S")
            except ValueError:
                continue

            # Template laden
            template = find_best_template_for_engine(engine_type, process_templates)
            template_events = template.get("timeline_events", [])

            dynamic_events = []
            current_date = ts_datetime.date()
            current_shift = 'AM' if ts_datetime.hour < 14 else 'PM'

            # Events generieren
            for event_def in template_events:
                duration = event_def.get("duration_shifts", 1)
                start_date_loop = current_date
                start_shift_loop = current_shift

                end_date_loop = start_date_loop
                end_shift_loop = start_shift_loop

                # KORREKTUR: Schleife läuft exakt 'duration' mal
                for _ in range(duration):
                    if end_shift_loop == 'AM':
                        end_shift_loop = 'PM'
                    else:
                        end_shift_loop = 'AM'
                        end_date_loop += timedelta(days=1)

                dynamic_events.append({
                    'title': event_def.get("name", "Unknown"),
                    'start': start_date_loop.strftime("%Y-%m-%d"),
                    'start_shift': start_shift_loop,
                    'end': end_date_loop.strftime("%Y-%m-%d"),
                    'end_shift': end_shift_loop,
                    'color': event_def.get("color", "grey")
                })

                # KORREKTUR: Nächstes Event startet direkt am Ende des aktuellen (keine Lücke)
                current_date = end_date_loop
                current_shift = end_shift_loop

            for event in dynamic_events:
                new_row.extend([
                    event['title'], event['start'], event['start_shift'],
                    event['end'], event['end_shift'], event['color']
                ])

            existing_projects[key] = new_row
            changes_made = True

    # 4. Sortierung beibehalten / neue Projekte einfügen
    final_rows_ordered = []
    seen_keys = set()

    # Zuerst die Projekte in der existierenden Reihenfolge
    for proj_key in current_project_order:
        if proj_key in existing_projects:
            final_rows_ordered.append(existing_projects[proj_key])
            seen_keys.add(proj_key)

    # Dann alle neuen Projekte anhängen
    for key, row_data in existing_projects.items():
        if key not in seen_keys:
            final_rows_ordered.append(row_data)

    # 5. Speichern
    try:
        with open(timeline_csv_path, 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.writer(f)
            writer.writerows(final_rows_ordered)
        return changes_made
    except Exception as e:
        print(f"FEHLER beim Schreiben der Timeline CSV: {e}")
        return False


def load_assigned_employees(file_path: str) -> List[Dict]:
    """Lädt manuelle Zuweisungen aus der angegebenen CSV-Datei."""
    if not os.path.exists(file_path):
        try:
            with open(file_path, 'w', newline='', encoding='utf-8-sig') as f:
                writer = csv.DictWriter(f, fieldnames=ASSIGNED_EMPLOYEES_FIELDNAMES)
                writer.writeheader()
            print(f"INFO: Leere Zuweisungsdatei '{file_path}' wurde erstellt.")
            return []
        except IOError as e:
            print(f"FEHLER beim Erstellen der Zuweisungsdatei '{file_path}': {e}")
            return []
    try:
        clean_assignments = []
        with open(file_path, mode='r', newline='', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            for row in reader:
                clean_row = {key: value for key, value in row.items() if key is not None}
                clean_assignments.append(clean_row)
        return clean_assignments
    except Exception as e:
        print(f"FEHLER beim Laden der manuellen Zuweisungen aus '{file_path}': {e}")
        return []


def save_assigned_employees(file_path: str, assignments_list: List[Dict]):
    """Speichert die Liste der manuellen Mitarbeiterzuweisungen in die angegebene CSV-Datei."""
    try:
        with open(file_path, mode='w', newline='', encoding='utf-8-sig') as file:
            writer = csv.DictWriter(file, fieldnames=ASSIGNED_EMPLOYEES_FIELDNAMES)
            writer.writeheader()
            for assignment in assignments_list:
                row_to_write = {key: assignment.get(key, '') for key in ASSIGNED_EMPLOYEES_FIELDNAMES}
                writer.writerow(row_to_write)
        return True
    except Exception as e:
        print(f"FEHLER beim Speichern der manuellen Zuweisungen in '{file_path}': {e}")
        return False


def get_employee_qualification_status_for_task_py(emp_quals: Optional[Dict], project: Dict, event_title: str) -> \
        Optional[str]:
    """
    Python-Pendant zur JavaScript-Funktion getEmployeeQualificationStatusForTask.
    Ermittelt den besten Qualifikationsstatus ('X', 'TS', 'TT', 'TP') eines Mitarbeiters
    für eine bestimmte Aufgabe.
    """
    if not emp_quals or not project:
        return None

    engine_type_clean = project.get('engine_type', '').strip().lower()
    event_title_clean = event_title.strip().lower()

    best_status = None
    status_priority = {'X': 1, 'TS': 2, 'TT': 3, 'TP': 4}

    for qual in emp_quals.get('merkmale', []):
        qual_name_clean = qual.get('merkmal', '').strip().lower()
        qual_value = qual.get('wert', '').upper()
        if not qual_value or qual_value not in status_priority:
            continue

        qual_parts = qual_name_clean.split('/')
        if len(qual_parts) < 2:
            continue

        qual_engine, qual_role = qual_parts[0].strip(), qual_parts[1].strip()

        # Motor-Typ-Abgleich (wie im JS)
        is_engine_match = False
        project_engine_base = engine_type_clean.split('e')[0]
        if qual_engine == project_engine_base:
            is_engine_match = True
        elif qual_engine in ['pw800', 'pw300', 'pw500'] and engine_type_clean.startswith(qual_engine[:3]):
            is_engine_match = True

        if not is_engine_match:
            continue

        # Rollen-Abgleich (wie im JS)
        is_role_match = False
        if 'mechanic' in qual_role and event_title_clean in ['rig', 'de-rig', 'atw']:
            is_role_match = True
        elif 'operator' in qual_role and event_title_clean == 'test':
            is_role_match = True

        if is_role_match:
            if best_status is None or status_priority[qual_value] < status_priority[best_status]:
                best_status = qual_value

    return best_status


def is_employee_available_for_slot_py(emp_id: str, slot_date_iso: str, slot_shift: str,
                                      employee_availability_map: Dict, global_assignments_by_slot: Dict,
                                      manual_assignments_in_slot: Dict) -> bool:
    """
    Prüft, ob ein Mitarbeiter für einen Slot verfügbar ist.
    Entspricht der Logik in isEmployeeAvailableForSlot im JS.
    """
    slot_key_global = (slot_date_iso, slot_shift)

    # 1. Ist der Mitarbeiter bereits für einen anderen Task in diesem Slot verplant?
    if emp_id in global_assignments_by_slot.get(slot_key_global, set()):
        return False

    # 2. Ist der Mitarbeiter manuell für einen anderen Task in diesem Slot geblockt?
    if emp_id in manual_assignments_in_slot.get(slot_key_global, set()):
        return False

    # 3. Ist der Mitarbeiter laut Schichtplan verfügbar?
    emp_availability = employee_availability_map.get(emp_id, {})
    day_info = emp_availability.get(slot_date_iso)
    if not day_info or not day_info.get('available') or day_info.get('shift') != slot_shift:
        return False

    return True


def is_employee_qualified_by_rules(emp_quals, project, event_title, assignment_rules):
    """
    Prüft anhand der dynamischen Regeln, ob ein Mitarbeiter qualifiziert ist.
    GIBT JETZT True/False ZURÜCK. Verwendet die zentrale VALID_ASSIGNABLE_STATUSES Konstante.
    """
    if not emp_quals:
        return False

    proj_engine_type = project.get('engine_type', '').strip()
    task_event_title = event_title.strip().upper()

    applicable_rules = []
    for rule in assignment_rules:
        event_match = not rule.get('event_titles') or task_event_title in rule.get('event_titles', [])
        engine_match = False
        if not rule.get('engine_types'):
            engine_match = True
        else:
            for rule_engine_prefix in rule.get('engine_types', []):
                if proj_engine_type.startswith(rule_engine_prefix):
                    engine_match = True
                    break
        if event_match and engine_match:
            applicable_rules.append(rule)

    if not applicable_rules:
        return False

    employee_valid_quals = {
        m['merkmal'] for m in emp_quals.get('merkmale', [])
        if m.get('wert') in VALID_ASSIGNABLE_STATUSES  # <-- Nutzung der neuen Konstante
    }

    for rule in applicable_rules:
        required_skills_for_rule = set(rule.get('required_skills', []))
        if required_skills_for_rule.issubset(employee_valid_quals):
            return True  # Ein Treffer genügt

    return False


def load_all_comments():
    global ALL_COMMENTS
    if not os.path.exists(COMMENTS_FILE_PATH):
        # Erstelle die Datei mit Header, wenn sie nicht existiert
        with open(COMMENTS_FILE_PATH, 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.writer(f)
            writer.writerow(['comment_id', 'project_id', 'author', 'timestamp', 'comment'])
        ALL_COMMENTS = []
        return
    try:
        with open(COMMENTS_FILE_PATH, mode='r', newline='', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            ALL_COMMENTS = list(reader)
        print(f"INFO: {len(ALL_COMMENTS)} Kommentare aus {COMMENTS_FILE_PATH} geladen.")
    except Exception as e:
        print(f"FEHLER beim Laden der Kommentare: {e}")
        ALL_COMMENTS = []


def save_all_comments():
    global ALL_COMMENTS
    fieldnames = ['comment_id', 'project_id', 'author', 'timestamp', 'comment']
    try:
        with open(COMMENTS_FILE_PATH, mode='w', newline='', encoding='utf-8-sig') as file:
            writer = csv.DictWriter(file, fieldnames=fieldnames, extrasaction='ignore')
            writer.writeheader()
            sorted_comments = sorted(ALL_COMMENTS, key=lambda x: x.get('timestamp', ''))
            writer.writerows(sorted_comments)
        return True
    except Exception as e:
        print(f"FEHLER beim Speichern der Kommentare: {e}")
        return False


def load_hardware_assignments(timeline_id: str) -> Dict:
    """Lädt manuelle Hardware-Kit-Zuweisungen für eine Timeline."""
    path = os.path.join(DB_ROOT_PATH, f"{timeline_id}_hardware_assignments.json")
    if not os.path.exists(path):
        return {}
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception:
        return {}


def save_hardware_assignments(timeline_id: str, assignments: Dict) -> bool:
    """Speichert Hardware-Kit-Zuweisungen für eine Timeline."""
    path = os.path.join(DB_ROOT_PATH, f"{timeline_id}_hardware_assignments.json")
    try:
        with _get_file_lock(path):
            with open(path, 'w', encoding='utf-8') as f:
                json.dump(assignments, f, indent=2, ensure_ascii=False)
        return True
    except Exception as e:
        print(f"FEHLER beim Speichern der Hardware-Zuweisungen: {e}")
        return False


def calculate_timeline_assignments(projects: List[Dict], employees: List[Dict], qualifications: List[Dict],
                                   manual_assignments: List[Dict], timeline_start_date: date, num_days: int,
                                   assignment_rules: List[Dict], assignment_groups: List[str] = None,
                                   selected_test_cell_ids: List[str] = None,
                                   timeline_id: str = None, previous_global_assignments: Dict = None,
                                   process_templates: Dict = None,
                                   available_hardware_kits: List[str] = None,
                                   manual_hw_assignments: Dict = None) -> Dict:
    if assignment_groups is None: assignment_groups = ['FF']
    if process_templates is None: process_templates = {}
    if available_hardware_kits is None: available_hardware_kits = []
    if manual_hw_assignments is None: manual_hw_assignments = {}

    def _event_assigns_rooms(engine_type, event_title):
        """Prüft ob ein Event Räume (Testzellen) zugewiesen bekommt.
        Primär aus dem Template; Fallback: alter hardcoded == 'test' Check."""
        template = find_best_template_for_engine(engine_type, process_templates)
        if template:
            for ev_def in template.get('timeline_events', []):
                if ev_def.get('name', '').strip().upper() == event_title.strip().upper():
                    return ev_def.get('assigns', {}).get('rooms', False)
        return event_title.strip().lower() == 'test'

    def _event_assigns_hardware(engine_type, event_title):
        """Prüft ob ein Event Hardware-Kits zugewiesen bekommt."""
        template = find_best_template_for_engine(engine_type, process_templates)
        if template:
            for ev_def in template.get('timeline_events', []):
                if ev_def.get('name', '').strip().upper() == event_title.strip().upper():
                    return ev_def.get('assigns', {}).get('hardware', False)
        return False

    def _event_releases_hardware(engine_type, event_title):
        """Prüft ob ein Event Hardware-Kits freigibt."""
        template = find_best_template_for_engine(engine_type, process_templates)
        if template:
            for ev_def in template.get('timeline_events', []):
                if ev_def.get('name', '').strip().upper() == event_title.strip().upper():
                    return ev_def.get('releases', {}).get('hardware', False)
        return False

    # --- 1. DATENAUFBEREITUNG ---
    all_locations_full = load_locations_and_events()
    all_locations_full = inject_dependency_blocks(all_locations_full)
    all_possible_cell_ids = [loc['ID'] for loc in all_locations_full]
    allowed_test_cell_ids = selected_test_cell_ids if selected_test_cell_ids is not None else all_possible_cell_ids

    employee_map_by_id = {e['id'].lower(): e for e in employees if e.get('id')}
    employee_qualifications_map = {q['id'].lower(): q for q in qualifications if q.get('id')}
    locations_map_with_events = {loc['ID']: loc for loc in all_locations_full}

    employee_availability_map = {}
    for emp in employees:
        if not emp.get('id'): continue
        availability = {}
        for week in emp.get('weeks', []):
            shift_from_csv = week.get('Schicht', '').upper()
            web_shift = 'AM' if shift_from_csv == 'F' else ('PM' if shift_from_csv == 'S' else None)
            if not web_shift: continue
            for day in week.get('days', []):
                if isinstance(day.get('date'), date):
                    availability[day['date'].isoformat()] = {'shift': web_shift,
                                                             'available': not day.get('ereignis', '').strip()}
        employee_availability_map[emp['id'].lower()] = availability

    manual_assignments_map_by_task, manual_assignments_in_slot = {}, {}
    for ma in manual_assignments:
        emp_id = ma.get('employee_id', '').lower();
        if not emp_id: continue
        task_key = (ma.get('project_code'), ma.get('engine_serial'), ma.get('event_title'), ma.get('date_str'),
                    ma.get('shift'))
        if task_key not in manual_assignments_map_by_task: manual_assignments_map_by_task[task_key] = []
        manual_assignments_map_by_task[task_key].append(emp_id)
        slot_key_global = (ma.get('date_str'), ma.get('shift'))
        if slot_key_global not in manual_assignments_in_slot: manual_assignments_in_slot[slot_key_global] = set()
        manual_assignments_in_slot[slot_key_global].add(emp_id)

    all_active_events = []
    for proj in projects:
        if proj.get('active'):
            for evt in proj.get('events', []):
                if 'start_datetime' in evt and 'end_datetime' in evt:
                    all_active_events.append({'project': proj, 'event': evt})
    all_active_events.sort(key=lambda x: datetime.fromisoformat(x['event']['start_datetime'].replace('Z', '')))

    # --- INIT GLOBALER STATE ---
    global_assignments_by_slot = {}
    if timeline_id:
        end_date_calc = timeline_start_date + timedelta(days=num_days)
        global_assignments_by_slot = global_scheduler.get_busy_employees_in_range(
            timeline_start_date, end_date_calc, ignore_timeline_id=timeline_id
        )

    # --- INIT SMART SCHEDULER ---
    try:
        history_data = global_scheduler.load_history()
        smart_scheduler = SmartScheduler(history_data, global_scheduler.assignments)
    except:
        smart_scheduler = None  # Fallback

    # --- 2. ZUWEISUNGS-LOOP ---
    cell_assignments_by_slot, assignments_by_event_slot = {}, {}

    # State für "Sticky" Zuweisung (Testzellen)
    sticky_cell_for_test_block = None

    for item_index, item in enumerate(all_active_events):
        project, event = item['project'], item['event']
        engine_type = project.get('engine_type', '')
        is_test_event = _event_assigns_rooms(engine_type, event.get('title', ''))

        # Sticky Logik für Testzellen
        if item_index > 0:
            prev_event = all_active_events[item_index - 1]['event']
            prev_engine = all_active_events[item_index - 1]['project'].get('engine_type', '')
            prev_is_test_event = _event_assigns_rooms(prev_engine, prev_event.get('title', ''))
            if not is_test_event or not prev_is_test_event or all_active_events[item_index - 1]['project'] != project:
                sticky_cell_for_test_block = None
        elif not is_test_event:
            sticky_cell_for_test_block = None

        try:
            start_dt = datetime.fromisoformat(event['start_datetime'].replace('Z', ''))
            end_dt = datetime.fromisoformat(event['end_datetime'].replace('Z', ''))
        except:
            continue

        current_dt, slot_index = start_dt, 0
        while current_dt < end_dt:
            slot_date_iso, slot_shift = current_dt.date().isoformat(), 'AM' if current_dt.hour < 12 else 'PM'
            slot_key_global = (slot_date_iso, slot_shift)

            if slot_key_global not in global_assignments_by_slot: global_assignments_by_slot[slot_key_global] = set()

            task_key_manual = (project.get('project_code'), project.get('engine_serial'), event.get('title'),
                               slot_date_iso, slot_shift)
            manually_assigned_ids = manual_assignments_map_by_task.get(task_key_manual, [])
            event_slot_key = f"{event['id']}_{slot_index}"

            common_data = {
                "test_cell_id": None,
                "date": slot_date_iso,
                "shift": slot_shift,
                "task": event.get('title', 'Unknown'),
                "engine": project.get('engine_type', 'N/A'),
                "project_name": project.get('name', 'N/A')
            }

            # A) Init Slot
            if manually_assigned_ids:
                assignments_by_event_slot[event_slot_key] = {
                    "employees": list(manually_assigned_ids),  # Kopie!
                    "is_manual": True,
                    **common_data
                }
                for emp_id in manually_assigned_ids: global_assignments_by_slot[slot_key_global].add(emp_id)
            else:
                assignments_by_event_slot[event_slot_key] = {
                    "employees": [],
                    "is_manual": False,
                    **common_data
                }

            # B) Testzellen Zuweisung
            if is_test_event:
                assigned_cell = None
                if sticky_cell_for_test_block:
                    assigned_cell = sticky_cell_for_test_block
                elif not assignments_by_event_slot[event_slot_key].get("test_cell_id"):
                    for cell_id in allowed_test_cell_ids:
                        loc = locations_map_with_events.get(cell_id)
                        if not loc: continue

                        # Blockaden Check
                        blocked = any((e['start_date'] < slot_date_iso or (
                                    e['start_date'] == slot_date_iso and e['start_shift'] <= slot_shift)) and
                                      (e['end_date'] > slot_date_iso or (
                                                  e['end_date'] == slot_date_iso and e['end_shift'] >= slot_shift))
                                      for e in loc.get('events', []) if e.get('type') in ['Maintenance', 'Blocked'])
                        if blocked: continue

                        # Typ Check
                        proj_eng = project.get('engine_type', '')
                        compatible = not loc.get('TYPES') or any(proj_eng.startswith(t) for t in loc.get('TYPES', []))
                        if not compatible: continue

                        # Verfügbarkeit
                        if cell_id not in cell_assignments_by_slot.get(slot_key_global, set()):
                            assigned_cell = cell_id
                            sticky_cell_for_test_block = cell_id
                            break

                if assigned_cell:
                    assignments_by_event_slot[event_slot_key]["test_cell_id"] = assigned_cell
                    if slot_key_global not in cell_assignments_by_slot: cell_assignments_by_slot[
                        slot_key_global] = set()
                    cell_assignments_by_slot[slot_key_global].add(assigned_cell)

            # C) SMART ASSIGNMENT (Mitarbeiter)
            max_employees_per_slot = 2
            current_assignment = assignments_by_event_slot[event_slot_key]

            if not current_assignment["is_manual"]:
                # 1. Kandidaten sammeln
                candidates = []
                for emp in employees:
                    eid = emp.get('id')
                    if not eid: continue
                    if emp.get('quali') not in assignment_groups: continue

                    if not is_employee_available_for_slot_py(eid, slot_date_iso, slot_shift, employee_availability_map,
                                                             global_assignments_by_slot, manual_assignments_in_slot):
                        continue

                    emp_quals = employee_qualifications_map.get(eid.lower())
                    if not is_employee_qualified_by_rules(emp_quals, project, event.get('title'), assignment_rules):
                        continue

                    status = get_employee_qualification_status_by_rules(emp_quals, project, event.get('title'),
                                                                        assignment_rules)

                    candidates.append({
                        "id": eid,
                        "name": emp.get('name'),
                        "quali_status": status,
                        "obj": emp
                    })

                # 2. Iteratives Auffüllen
                current_assigned_objects = []

                # Wenn schon jemand drin ist (durch Teil-Manuell o.ä.), übernehmen
                for existing_id in current_assignment["employees"]:
                    emp_obj = employee_map_by_id.get(existing_id)
                    if emp_obj:
                        # Quali holen für Score-Logik
                        q = employee_qualifications_map.get(existing_id)
                        s = get_employee_qualification_status_by_rules(q, project, event.get('title'), assignment_rules)
                        current_assigned_objects.append(
                            {"id": existing_id, "name": emp_obj.get('name'), "quali_status": s})

                while len(current_assignment["employees"]) < max_employees_per_slot and candidates:

                    ctx = {
                        "date_obj": current_dt.date(),
                        "engine": project.get('engine_type'),
                        "project_code": project.get('project_code'),
                        "task": event.get('title')
                    }

                    scored_candidates = []
                    for cand in candidates:
                        ctx['quali_status'] = cand['quali_status']
                        score = 0
                        if smart_scheduler:
                            score = smart_scheduler.calculate_score(cand, ctx, current_assigned_objects)
                        else:
                            score = 100 if cand['quali_status'] == 'X' else 50

                        # === OPTION 1: STICKY MEMORY BONUS (Das "Gedächtnis") ===
                        if previous_global_assignments:
                            shift_data = previous_global_assignments.get(slot_date_iso, {}).get(slot_shift, {})
                            if cand['id'].lower() in shift_data:
                                prev_details = shift_data[cand['id'].lower()]
                                # Prüfen, ob er genau für DASSELBE Projekt/Task eingeteilt war
                                if prev_details.get('project') == project.get('name') and prev_details.get(
                                        'task') == event.get('title'):
                                    score += 10000  # Massiver Bonus, damit er dort bleibt!

                        scored_candidates.append((score, cand))

                    # === DETERMINISTISCHE SORTIERUNG (Der Tie-Breaker) ===
                    # Sortiert 1. nach Score (absteigend: -x[0])
                    # und 2. nach ID (aufsteigend: x[1]['id']) als Schiedsrichter bei Gleichstand
                    scored_candidates.sort(key=lambda x: (-x[0], x[1]['id'].lower()))

                    # Besten auswählen
                    best_score, best_cand = scored_candidates[0]

                    # Zuweisen
                    current_assignment["employees"].append(best_cand['id'])
                    global_assignments_by_slot[slot_key_global].add(best_cand['id'])

                    current_assigned_objects.append(best_cand)
                    candidates.remove(best_cand)

            current_dt += timedelta(hours=12)
            slot_index += 1

    # 3. Footer berechnen
    footer_availability = {}
    timeline_start_datetime = datetime.combine(timeline_start_date, time(0, 0))
    for i in range(num_days * 2):
        col_dt = timeline_start_datetime + timedelta(hours=i * 12)
        slot_date_iso, slot_shift = col_dt.date().isoformat(), 'AM' if col_dt.hour < 12 else 'PM'
        slot_key_global = (slot_date_iso, slot_shift)

        available_ids = [e.get('id') for e in employees if
                         e.get('quali') in assignment_groups and is_employee_available_for_slot_py(e.get('id'),
                                                                                                   slot_date_iso,
                                                                                                   slot_shift,
                                                                                                   employee_availability_map,
                                                                                                   global_assignments_by_slot,
                                                                                                   manual_assignments_in_slot)]

        manual_blockers_in_slot = [e_id for e_id in manual_assignments_in_slot.get(slot_key_global, set()) if
                                   e_id not in global_assignments_by_slot.get(slot_key_global, set())]

        footer_availability[i] = {
            "available": [employee_map_by_id.get(eid.lower(), {}).get('initials', '??') for eid in available_ids],
            "manual": [employee_map_by_id.get(eid.lower(), {}).get('initials', '??') for eid in manual_blockers_in_slot]
        }

    # Rückgabe vorbereiten: Set in Liste umwandeln für JSON
    blocked_map_json = {}
    for k, v in global_assignments_by_slot.items():
        blocked_map_json[f"{k[0]}_{k[1]}"] = list(v)

    # --- HARDWARE-KIT ZUWEISUNG ---
    # Reservierungslogik: Kit wird beim ersten assigns.hardware=True Event belegt und
    # erst beim Ende des releases.hardware=True Events freigegeben.
    # Während der gesamten Zeit (inkl. Lücken zwischen Events) ist das Kit blockiert.
    hw_assignments_result = {}  # event_id → {'kit_id': ..., 'is_manual': bool}
    if available_hardware_kits:
        hw_data = load_test_equipment_data()
        hw_mappings = (hw_data or {}).get('mappings', {})
        boms_ref = (hw_data or {}).get('boms', {})
        available_kit_set = set(available_hardware_kits)

        project_sticky_kit = {}   # project_id → kit_id (aktive Reservierung)
        project_kit_start = {}    # project_id → start_dt der ersten Zuweisung
        kit_busy_intervals: Dict[str, List] = {}  # kit_id → [(start_dt, end_dt)] bereits abgeschlossene Intervalle

        for item in all_active_events:
            project, event = item['project'], item['event']
            engine_type = project.get('engine_type', '')
            project_id = f"{project.get('project_code', '')}_{project.get('engine_serial', '')}"
            event_id = event.get('id', '')

            assigns_hw = _event_assigns_hardware(engine_type, event.get('title', ''))
            releases_hw = _event_releases_hardware(engine_type, event.get('title', ''))

            if not assigns_hw and not releases_hw:
                continue

            try:
                start_dt = datetime.fromisoformat(event['start_datetime'].replace('Z', ''))
                end_dt = datetime.fromisoformat(event['end_datetime'].replace('Z', ''))
            except Exception:
                continue

            if assigns_hw:
                manual_entry = manual_hw_assignments.get(event_id, {})
                manual_kit = manual_entry.get('kit_id') if manual_entry.get('is_manual') else None

                if manual_kit and manual_kit in available_kit_set:
                    assigned_kit = manual_kit
                    if project_id not in project_sticky_kit:
                        project_kit_start[project_id] = start_dt
                    project_sticky_kit[project_id] = assigned_kit
                elif project_id in project_sticky_kit:
                    assigned_kit = project_sticky_kit[project_id]
                else:
                    # Auto-assign: kompatibles, nicht belegtes Kit finden
                    engine_compatible = set(hw_mappings.get(engine_type, []))
                    if not engine_compatible:
                        for map_key, map_kits in hw_mappings.items():
                            if engine_type.startswith(map_key) or map_key.startswith(engine_type):
                                engine_compatible.update(map_kits)
                    candidates = list(engine_compatible & available_kit_set)
                    candidates.sort(key=lambda k: boms_ref.get(k, {}).get('code', 'ZZZ'))

                    assigned_kit = None
                    for kid in candidates:
                        # Prüfe abgeschlossene Reservierungen
                        finished_intervals = kit_busy_intervals.get(kid, [])
                        conflict = any(rs < end_dt and re > start_dt for rs, re in finished_intervals)
                        if not conflict:
                            # Prüfe laufende Reservierungen anderer Projekte (noch nicht freigegeben)
                            for other_pid, other_kit in project_sticky_kit.items():
                                if other_kit == kid and other_pid != project_id:
                                    # Laufende Reservierung beginnt bei project_kit_start[other_pid]
                                    # und endet unbekannt → gilt als Konflikt solange start_dt nach Reservierungsstart
                                    other_start = project_kit_start.get(other_pid)
                                    if other_start is not None and other_start < end_dt:
                                        conflict = True
                                        break
                        if not conflict:
                            assigned_kit = kid
                            break

                    if assigned_kit:
                        project_sticky_kit[project_id] = assigned_kit
                        project_kit_start[project_id] = start_dt

                if assigned_kit:
                    hw_assignments_result[event_id] = {
                        'kit_id': assigned_kit,
                        'is_manual': bool(manual_kit and manual_kit == assigned_kit)
                    }

            if releases_hw and project_id in project_sticky_kit:
                # Reservierung abschließen: Kit ist von Beginn der ersten Zuweisung
                # bis zum Ende dieses Release-Events belegt
                released_kit = project_sticky_kit[project_id]
                res_start = project_kit_start.get(project_id, start_dt)
                kit_busy_intervals.setdefault(released_kit, []).append((res_start, end_dt))
                project_sticky_kit.pop(project_id, None)
                project_kit_start.pop(project_id, None)

    return {
        "assignments": assignments_by_event_slot,
        "footer_availability": footer_availability,
        "blocked_employees": blocked_map_json,
        "hardware_assignments": hw_assignments_result
    }

def migrate_deviation_model(data: Dict) -> Dict:
    """
    Migriert das alte Deviation-Modell (type='deviation' als Baumknoten) ins neue Format
    (deviations[] Array direkt am Eltern-Schritt). Markiert die Datei nach Migration mit
    _deviation_model_v2=True, damit sie nicht erneut migriert wird.
    """
    if not data or 'steps' not in data:
        return data

    steps = data['steps']
    old_types = {'deviation', 'troubleshooting', 'solution'}

    if not any(s.get('type') in old_types for s in steps):
        data['_deviation_model_v2'] = True
        return data

    step_map = {s['id_str']: s for s in steps if 'id_str' in s}

    deviation_steps = [s for s in steps if s.get('type') == 'deviation']
    for dev_step in deviation_steps:
        parent_id = dev_step.get('parent_id_str')
        if not parent_id or parent_id not in step_map:
            continue
        parent_step = step_map[parent_id]
        if 'deviations' not in parent_step:
            parent_step['deviations'] = []

        dev_num = len(parent_step['deviations']) + 1
        reported_at = ''
        if dev_step.get('history'):
            reported_at = dev_step['history'][0].get('timestamp', '')
        reported_by = ''
        if dev_step.get('history'):
            reported_by = dev_step['history'][0].get('user', '')

        dev_obj = {
            'id': f"DEV-{dev_num:03d}",
            'status': 'Closed' if dev_step.get('status') == 'Completed' else 'Open',
            'reason': dev_step.get('name', 'Unbekannte Abweichung'),
            'hub_incident': dev_step.get('hub_incident', ''),
            'reported_by': reported_by,
            'reported_at': reported_at,
            'thread': []
        }

        # Troubleshooting- und Solution-Kinder in thread[] konvertieren
        children = sorted(
            [s for s in steps if s.get('parent_id_str') == dev_step.get('id_str')],
            key=lambda x: x.get('id_str', '')
        )
        for child in children:
            entry_type = child.get('type', 'troubleshooting')
            if entry_type not in ('troubleshooting', 'solution'):
                continue
            ts = child['history'][0].get('timestamp', '') if child.get('history') else ''
            user = child['history'][0].get('user', '') if child.get('history') else ''
            dev_obj['thread'].append({
                'id': f"t-{child['id_str'].replace('.', '-')}",
                'type': entry_type,
                'text': child.get('name', ''),
                'author': user,
                'date': ts
            })

        parent_step['deviations'].append(dev_obj)

    # Alte deviation/troubleshooting/solution Knoten entfernen
    data['steps'] = [s for s in steps if s.get('type') not in old_types]
    data['_deviation_model_v2'] = True
    return data


def find_origin_timeline(project_code: str, engine_serial: str) -> Optional[str]:
    """
    Sucht in allen konfigurierten Timeline-CSVs nach einem Projekt anhand von
    project_code + engine_serial und gibt die zugehörige timeline_id zurück.
    Wird verwendet, um origin_timeline nachträglich zu setzen.
    """
    if not project_code or not engine_serial:
        return None
    try:
        config = load_app_config()
        for tl in config.get('timelines', []):
            tl_id = tl.get('id')
            if not tl_id:
                continue
            data_file = tl.get('data_file')
            data_files = [data_file] if data_file else tl.get('data_files', [])
            data_files = [f for f in data_files if f]
            for csv_path in data_files:
                if not os.path.exists(csv_path):
                    continue
                try:
                    with open(csv_path, newline='', encoding='utf-8-sig') as f:
                        reader = csv.reader(f)
                        for row in reader:
                            if len(row) >= 4 and row[2].strip() == project_code and row[3].strip() == engine_serial:
                                return tl_id
                except Exception:
                    continue
    except Exception:
        pass
    return None


def migrate_project_status_files():
    """Läuft beim Startup: fügt UUID hinzu, migriert das Deviation-Modell und setzt origin_timeline für alle JSONs."""
    if not os.path.exists(PROJECT_STATUS_DIR):
        return
    migrated = 0
    for filename in os.listdir(PROJECT_STATUS_DIR):
        if not filename.endswith('.json'):
            continue
        filepath = os.path.join(PROJECT_STATUS_DIR, filename)
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
            changed = False
            if not data.get('uuid'):
                data['uuid'] = str(uuid.uuid4())
                changed = True
            if not data.get('_deviation_model_v2'):
                data = migrate_deviation_model(data)
                changed = True
            if not data.get('origin_timeline'):
                origin = find_origin_timeline(data.get('project_code', ''), data.get('engine_serial', ''))
                if origin:
                    data['origin_timeline'] = origin
                    changed = True
            if changed:
                save_json_file(filepath, data)
                migrated += 1
        except Exception as e:
            print(f"WARNUNG: Migration fehlgeschlagen für {filename}: {e}")
    if migrated:
        print(f"INFO: {migrated} Projekt-Status-Datei(en) migriert (UUID + Deviation-Modell + origin_timeline).")


def get_or_create_project_status_details(project_data: Dict) -> Optional[Dict]:
    if not project_data: return None

    project_code = project_data.get('project_code', 'UNKNOWN')
    engine_serial = project_data.get('engine_serial', 'UNKNOWN')
    engine_type = project_data.get('engine_type')

    raw_project_id = f"{project_code}_{engine_serial}"
    safe_project_id = re.sub(r'[^a-zA-Z0-9_-]', '', raw_project_id)
    status_file_path = os.path.join('db', 'projectstatus', f'project_status_{safe_project_id}.json')

    if os.path.exists(status_file_path):
        try:
            with open(status_file_path, 'r', encoding='utf-8') as f:
                loaded_data = json.load(f)
            changed = False
            if not loaded_data.get('uuid'):
                loaded_data['uuid'] = str(uuid.uuid4())
                changed = True
            if not loaded_data.get('_deviation_model_v2'):
                loaded_data = migrate_deviation_model(loaded_data)
                changed = True
            if changed:
                save_json_file(status_file_path, loaded_data)
            loaded_data['project_code'] = project_code
            loaded_data['engine_serial'] = engine_serial
            return loaded_data
        except Exception as e:
            print(
                f"WARNUNG: Status-JSON für {safe_project_id} konnte nicht gelesen werden, wird neu generiert. Fehler: {e}")

    templates = load_process_templates()
    template = find_best_template_for_engine(engine_type, templates)

    project_tasks = project_data.get('events', [])
    due_date = None
    if project_tasks:
        try:
            project_tasks_with_end_dt = [t for t in project_tasks if 'end_datetime' in t]
            if project_tasks_with_end_dt:
                project_tasks_with_end_dt.sort(key=lambda t: datetime.fromisoformat(t['end_datetime']))
                last_task_end_dt = datetime.fromisoformat(project_tasks_with_end_dt[-1]['end_datetime'])
                due_date = last_task_end_dt.date()
        except Exception:
            pass
    due_date_display = due_date.strftime("%d. %b %Y") if due_date else "N/A"

    steps_with_status = []
    for step_config in template.get('steps', []):
        steps_with_status.append({
            "id_str": step_config.get('id'), "parent_id_str": step_config.get('parent_id'),
            "type": "main", "is_expanded": True, "name": step_config.get('name'), "status": "Upcoming",
            "status_detail": "", "notes": "", "last_updated_by": None, "last_updated_at": None, "history": []
        })

    current_step_for_notes_obj = None
    if steps_with_status:
        sorted_steps = sorted(steps_with_status, key=lambda s: [int(p) for p in s['id_str'].split('.')])
        first_step = sorted_steps[0]
        current_step_for_notes_obj = {"name": first_step['name'], "id_str": first_step['id_str']}

    new_status = {
        "name": project_data.get("name"),
        "engine_type": engine_type,
        "uuid": str(uuid.uuid4()),
        "engine_serial": project_data.get("engine_serial", "N/A"),
        "project_code": project_code,
        "customer": project_data.get("customer", "N/A"),
        "due_date_display": due_date_display,
        "progress_percent": 0,
        "steps": steps_with_status,
        "current_step_for_notes": current_step_for_notes_obj,
        "_deviation_model_v2": True
    }
    try:
        os.makedirs(os.path.dirname(status_file_path), exist_ok=True)
        save_json_file(status_file_path, new_status)
    except Exception as e:
        print(f"WARNUNG: Neues Projekt-JSON für {safe_project_id} konnte nicht gespeichert werden: {e}")
    return new_status


def get_employee_dashboard_data():
    if not ALL_EMPLOYEES or not ALL_QUALIFICATIONS:
        return {
            "total_employees": 0, "total_groups": 0, "total_features": 0,
            "employees_list": [], "groups_list": [], "skills_list": [],
            "full_employee_data": [], "full_qualification_data": []
        }

    df_emp = pd.DataFrame(ALL_EMPLOYEES)

    # KPIs berechnen
    total_employees = len(df_emp)
    total_groups = len(df_emp['quali'].dropna().unique())
    all_merkmale = {m['merkmal'] for q in ALL_QUALIFICATIONS for m in q.get('merkmale', [])}
    total_features = len(all_merkmale)

    # Filter-Listen erstellen
    employees_list = sorted(
        [{'name': name, 'id': eid.lower()} for name, eid in zip(df_emp['name'], df_emp['id']) if eid],
        key=lambda x: x['name'])
    groups_list = sorted(list(df_emp['quali'].dropna().unique()))
    skills_list = sorted(list(set(
        merkmal_string.split('/')[1]
        for merkmal_string in all_merkmale
        if isinstance(merkmal_string, str) and '/' in merkmal_string
    )))

    import copy
    employees_for_json = copy.deepcopy(ALL_EMPLOYEES)

    # Wir durchlaufen die Kopie und konvertieren alle Datumsobjekte in ISO-Strings.
    for emp in employees_for_json:
        if 'weeks' in emp:
            for week in emp['weeks']:
                if 'days' in week:
                    for day in week['days']:
                        if isinstance(day.get('date'), date):
                            day['date'] = day['date'].isoformat()  # z.B. date(2023, 11, 9) -> "2023-11-09"

    return {
        "total_employees": total_employees,
        "total_groups": total_groups,
        "total_features": total_features,
        "employees_list": employees_list,
        "groups_list": groups_list,
        "skills_list": skills_list,
        "full_employee_data": employees_for_json,  # Diese Liste enthält jetzt Strings statt date-Objekte
        "full_qualification_data": ALL_QUALIFICATIONS
    }


def load_assignment_rules(file_path: str):
    """Lädt die Zuordnungsregeln aus der angegebenen CSV-Datei."""
    if not os.path.exists(file_path):
        # Erstelle eine leere Datei mit Header, wenn sie nicht existiert
        try:
            with open(file_path, 'w', newline='', encoding='utf-8-sig') as f:
                writer = csv.writer(f)
                writer.writerow(ASSIGNMENT_RULES_FIELDNAMES)
            print(f"INFO: Leere Regeldatei '{file_path}' wurde erstellt.")
            return []
        except IOError as e:
            print(f"FEHLER beim Erstellen der Regeldatei '{file_path}': {e}")
            return []

    try:
        rules = []
        with open(file_path, mode='r', newline='', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            for row in reader:
                row['event_titles'] = [t.strip() for t in row.get('event_titles', '').split(',') if t.strip()]
                row['engine_types'] = [t.strip() for t in row.get('engine_types', '').split(',') if t.strip()]
                row['required_skills'] = [s.strip() for s in row.get('required_skills', '').split(',') if s.strip()]
                rules.append(row)
        return rules
    except Exception as e:
        print(f"FEHLER beim Laden der Zuordnungsregeln aus '{file_path}': {e}")
        return []


def save_assignment_rules(rules: List[Dict], file_path: str):
    """Speichert die Zuordnungsregeln in die angegebene CSV-Datei."""
    try:
        with open(file_path, mode='w', newline='', encoding='utf-8-sig') as file:
            writer = csv.DictWriter(file, fieldnames=ASSIGNMENT_RULES_FIELDNAMES)
            writer.writeheader()
            for rule in rules:
                rule_to_write = rule.copy()
                rule_to_write['event_titles'] = ','.join(rule.get('event_titles', []))
                rule_to_write['engine_types'] = ','.join(rule.get('engine_types', []))
                rule_to_write['required_skills'] = ','.join(rule.get('required_skills', []))
                writer.writerow(rule_to_write)
        return True
    except Exception as e:
        print(f"FEHLER beim Speichern der Zuordnungsregeln in '{file_path}': {e}")
        return False


def is_employee_qualified_by_rules(emp_quals, project, event_title, assignment_rules):
    """
    Prüft anhand der dynamischen Regeln, ob ein Mitarbeiter qualifiziert ist.
    GIBT JETZT True/False ZURÜCK. Verwendet die zentrale VALID_ASSIGNABLE_STATUSES Konstante.
    """
    if not emp_quals:
        return False

    proj_engine_type = project.get('engine_type', '').strip()
    task_event_title = event_title.strip().upper()

    applicable_rules = []
    for rule in assignment_rules:
        event_match = not rule.get('event_titles') or task_event_title in rule.get('event_titles', [])
        engine_match = False
        if not rule.get('engine_types'):
            engine_match = True
        else:
            for rule_engine_prefix in rule.get('engine_types', []):
                if proj_engine_type.startswith(rule_engine_prefix):
                    engine_match = True
                    break
        if event_match and engine_match:
            applicable_rules.append(rule)

    if not applicable_rules:
        return False
    employee_valid_quals = {
        m['merkmal'] for m in emp_quals.get('merkmale', [])
        if m.get('wert') in VALID_ASSIGNABLE_STATUSES  # <-- Nutzung der neuen Konstante
    }

    for rule in applicable_rules:
        required_skills_for_rule = set(rule.get('required_skills', []))
        if required_skills_for_rule.issubset(employee_valid_quals):
            return True  # Ein Treffer genügt

    return False


def get_employee_qualification_status_by_rules(emp_quals: Optional[Dict], project: Dict, event_title: str,
                                               assignment_rules: List[Dict]) -> Optional[str]:
    """
    (VOLLSTÄNDIG KORRIGIERTE VERSION)
    Ermittelt den besten Qualifikationsstatus ('X', 'TS', 'TT', 'TP'), aber nur, wenn der Mitarbeiter
    auch tatsächlich nach den Regeln als qualifiziert gilt.
    """
    if not emp_quals:
        return None

    proj_engine_type = project.get('engine_type', '').strip()
    task_event_title = event_title.strip().upper()

    # 1. Anwendbare Regeln finden (Logik unverändert)
    applicable_rules = []
    for rule in assignment_rules:
        event_match = not rule.get('event_titles') or task_event_title in rule.get('event_titles', [])
        engine_match = False
        if not rule.get('engine_types'):
            engine_match = True
        else:
            for rule_engine_prefix in rule.get('engine_types', []):
                if proj_engine_type.startswith(rule_engine_prefix):
                    engine_match = True
                    break
        if event_match and engine_match and rule.get('required_skills'):
            applicable_rules.append(rule)

    if not applicable_rules:
        return None

    # 2. Hole die Skills des Mitarbeiters
    #    a) Eine Map mit allen Skills und ihren Werten (auch 'TP' etc.)
    employee_skills_map = {m.get('merkmal'): m.get('wert') for m in emp_quals.get('merkmale', [])}
    #    b) Ein Set NUR der Skills, die einen gültigen Zuweisungsstatus haben
    valid_assignable_skills_set = {
        m['merkmal'] for m in emp_quals.get('merkmale', [])
        if m.get('wert') in VALID_ASSIGNABLE_STATUSES  # <-- Nutzung der neuen Konstante
    }

    # 3. Finde den besten Status über alle Regeln hinweg, die der Mitarbeiter ERFÜLLT
    best_overall_status = None
    status_priority = {'X': 1, 'TS': 2, 'TT': 3, 'TP': 4}

    for rule in applicable_rules:
        required_skills_set = set(rule.get('required_skills', []))

        if required_skills_set.issubset(valid_assignable_skills_set):

            current_rule_status = 'X'  # Startannahme
            highest_priority_num = 0

            for skill in required_skills_set:
                skill_status = employee_skills_map.get(skill)

                if skill_status in status_priority and status_priority[skill_status] > highest_priority_num:
                    highest_priority_num = status_priority[skill_status]
                    current_rule_status = skill_status

            if best_overall_status is None or status_priority[current_rule_status] < status_priority[
                best_overall_status]:
                best_overall_status = current_rule_status

    return best_overall_status


def load_locations_and_events(locations_path=LOCATIONS_FILE_PATH, events_path=LOCATIONS_EVENTS_CSV_PATH):
    """
    Lädt Testzellen UND ihre manuell hinterlegten Events (Wartung, etc.).
    Dies ist die Basis für weitere Anreicherungen.
    """
    test_cells = {}
    if not os.path.exists(locations_path):
        print(f"WARNUNG: Standort-Datei {locations_path} nicht gefunden.")
        return []

    try:
        # 1. Testzellen-Stammdaten laden
        with open(locations_path, mode='r', newline='', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            for row in reader:
                row['TYPES'] = [t.strip() for t in row.get('TYPES', '').split(',') if t.strip()]
                row['REQUIRED_UNITS'] = [u.strip() for u in row.get('REQUIRED_UNITS', '').split(',') if u.strip()]

                row['events'] = []
                test_cells[row['ID']] = row

        # 2. Manuelle Events laden und zuordnen
        if os.path.exists(events_path):
            with open(events_path, mode='r', newline='', encoding='utf-8-sig') as file:
                reader = csv.DictReader(file)
                for row in reader:
                    cell_id = row.get('cell_id')
                    if cell_id in test_cells:
                        # NEU: Sicherstellen, dass neue Felder existieren, falls alte CSV-Datei verwendet wird
                        row['created_by'] = row.get('created_by', 'System')
                        row['created_at'] = row.get('created_at', '')
                        row['last_modified_by'] = row.get('last_modified_by', '')
                        row['last_modified_at'] = row.get('last_modified_at', '')
                        test_cells[cell_id]['events'].append(row)

        return list(test_cells.values())
    except Exception as e:
        print(f"FEHLER beim Laden von Standorten und Events: {e}")
        return []


def aggregate_operational_events(locations_with_manual_events, projects):
    """
    Reichert die Testzellen-Daten mit 'Operational' Events aus der Projekt-Timeline an.
    """
    locations_map = {loc['ID']: loc for loc in locations_with_manual_events}

    for proj in projects:
        if not proj.get('active'):
            continue

        # Finde die 'Test' Events in diesem Projekt
        project_test_events = [evt for evt in proj.get('events', []) if evt.get('title', '').lower() == 'test']

        for event_data in project_test_events:
            test_cell_id = event_data.get('assigned_test_cell_id')  # Fiktives Feld, das wir in der API-Route füllen

            if test_cell_id and test_cell_id in locations_map:
                operational_event = {
                    "event_id": f"op_{event_data.get('id')}",
                    "cell_id": test_cell_id,
                    "title": proj.get('project_code', 'Unbekanntes Projekt'),
                    "type": "Operational",
                    "start_date": event_data.get('start'),
                    "start_shift": event_data.get('start_shift'),
                    "end_date": event_data.get('end'),
                    "end_shift": event_data.get('end_shift'),
                    "description": f"Testlauf für {proj.get('name')}"
                }
                locations_map[test_cell_id]['events'].append(operational_event)

    return list(locations_map.values())


def get_dynamic_status_for_locations(locations_with_all_events):
    """
    Berechnet den aktuellen 'STATUS' für jede Testzelle,
    damit die alte Planungslogik weiterhin funktioniert.
    """
    now = datetime.now()
    today_iso = now.date().isoformat()
    current_shift = 'AM' if now.hour < 12 else 'PM'

    for cell in locations_with_all_events:
        cell['STATUS'] = 'AVAILABLE'  # Standard-Annahme
        for event in cell.get('events', []):
            # Prüfen, ob das Event jetzt gerade aktiv ist
            start_ok = (event['start_date'] < today_iso) or (
                    event['start_date'] == today_iso and event['start_shift'] <= current_shift)
            end_ok = (event['end_date'] > today_iso) or (
                    event['end_date'] == today_iso and event['end_shift'] >= current_shift)

            if start_ok and end_ok:
                event_type = event.get('type')
                if event_type == 'Blocked':
                    cell['STATUS'] = 'BLOCKED'
                    break  # Blocked hat die höchste Priorität
                elif event_type == 'Maintenance':
                    cell['STATUS'] = 'MAINTENANCE'
                    # Weiter suchen, falls noch ein 'Blocked' Event kommt
    return locations_with_all_events


def save_locations_and_events(data, locations_path=LOCATIONS_FILE_PATH, events_path=LOCATIONS_EVENTS_CSV_PATH):
    """
    Speichert die Testzellen-Stammdaten und die manuellen Events in die jeweiligen CSVs.
    """
    cell_headers = ['ID', 'TESTCELL', 'TYPES', 'REQUIRED_UNITS']
    event_headers = ['event_id', 'cell_id', 'title', 'type', 'start_date', 'start_shift', 'end_date', 'end_shift',
                     'description', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at']

    all_manual_events = []
    cells_to_write = []

    for cell in data:
        cell_copy = {
            'ID': cell.get('ID'),
            'TESTCELL': cell.get('TESTCELL'),
            'TYPES': ','.join(cell.get('TYPES', [])),
            # NEU: Abhängigkeiten speichern
            'REQUIRED_UNITS': ','.join(cell.get('REQUIRED_UNITS', []))
        }
        cells_to_write.append(cell_copy)

        for event in cell.get('events', []):
            # WICHTIG: Virtuelle Events NICHT speichern!
            if event.get('isVirtual'): continue

            # WICHTIG: Archivierte Events NICHT wieder in die Live-CSV speichern!
            if event.get('isArchived'): continue

            if event.get('type') in ['Maintenance', 'Blocked']:
                # ... (Der Rest bleibt wie er ist) ...
                event_to_save = {h: event.get(h, '') for h in event_headers}
                all_manual_events.append(event_to_save)

    try:
        with open(locations_path, mode='w', newline='', encoding='utf-8-sig') as file:
            writer = csv.DictWriter(file, fieldnames=cell_headers)
            writer.writeheader()
            writer.writerows(cells_to_write)

        with open(events_path, mode='w', newline='', encoding='utf-8-sig') as file:
            writer = csv.DictWriter(file, fieldnames=event_headers)
            writer.writeheader()
            writer.writerows(all_manual_events)
        return True
    except IOError as e:
        print(f"FEHLER beim Speichern der Testzellen-Daten: {e}")
        return False


def inject_dependency_blocks(locations_list):
    """
    Injiziert virtuelle 'Blocked'-Events in Testzellen, wenn verknüpfte
    Betriebseinheiten (Units) durch ein kritisches Issue gesperrt sind.
    """
    # Schutz vor None-Input
    if locations_list is None:
        return []

    # 1. Alle Orders laden
    m_data = load_maintenance_data()
    orders = m_data.get('orders', [])

    # 2. Map erstellen: UnitUUID -> Liste von Blocker-Issues
    unit_blocker_map = {}

    for o in orders:
        # Prüfen auf Unit Blocker (neue Logik + Legacy Fallback)
        is_unit_blocker = o.get('isUnitBlocker')
        if is_unit_blocker is None:
            is_blocker = o.get('isBlocker')
            has_blocked_assets = bool(o.get('blockedAssetIds'))
            is_unit_blocker = is_blocker and not has_blocked_assets

        # Nur relevante Issues betrachten
        if o.get('type') == 'I' and is_unit_blocker:
            unit_id = o.get('facilityUnitId')
            if unit_id:
                if unit_id not in unit_blocker_map: unit_blocker_map[unit_id] = []
                unit_blocker_map[unit_id].append(o)

    # 3. Events injizieren
    today_iso = date.today().isoformat()

    for loc in locations_list:
        required_units = loc.get('REQUIRED_UNITS', [])
        # Sicherstellen, dass required_units eine Liste ist (falls CSV Fehler hat)
        if isinstance(required_units, str):
            required_units = [required_units]

        for req_unit_id in required_units:
            if req_unit_id in unit_blocker_map:
                issues = unit_blocker_map[req_unit_id]

                for issue in issues:
                    # Startdatum & Schicht
                    start_date = issue.get('date', today_iso)
                    start_shift = issue.get('startShift', 'AM')

                    # Enddatum & Schicht
                    if issue.get('status') == 'Completed':
                        end_date = issue.get('completedDate', today_iso)
                        end_shift = issue.get('completedShift', 'PM')
                    else:
                        end_date = '2099-12-31'
                        end_shift = 'PM'

                    virtual_event = {
                        "event_id": f"virt_block_{issue.get('id')}_{loc['ID']}",
                        "cell_id": loc['ID'],
                        "title": f"⛔ Sperre: {issue.get('plantIndex') or 'Anlage'}",
                        "type": "Blocked",
                        "start_date": start_date,
                        "start_shift": start_shift,
                        "end_date": end_date,
                        "end_shift": end_shift,
                        "description": f"Automatische Sperre durch Issue {issue.get('id')}: {issue.get('task')}",
                        "isVirtual": True,
                        "sourceIssueId": issue.get('id')
                    }

                    if 'events' not in loc: loc['events'] = []
                    loc['events'].append(virtual_event)

    return locations_list

def docx_find_replace(doc, replacements):
    """
    Sucht und ersetzt Text in einem docx-Dokument (Absätze und Tabellen),
    auch wenn die Platzhalter über mehrere "Runs" aufgeteilt sind.
    """
    for p in doc.paragraphs:
        if any(key in p.text for key in replacements):
            full_text = "".join(run.text for run in p.runs)
            original_full_text = full_text
            for key, value in replacements.items():
                full_text = full_text.replace(key, value)

            if original_full_text != full_text:
                style = p.runs[0].style if p.runs else None
                original_font = p.runs[0].font if p.runs and p.runs[0].font else None
                p.clear()

                new_run = p.add_run(full_text)
                if style:
                    new_run.style = style
                if original_font:
                    new_run.font.name = original_font.name
                    new_run.font.size = original_font.size
                    new_run.font.bold = original_font.bold
                    new_run.font.italic = original_font.italic
                    new_run.font.underline = original_font.underline
                    if original_font.color and original_font.color.rgb:
                        new_run.font.color.rgb = original_font.color.rgb

    for table in doc.tables:
        for row in table.rows:
            for cell in row.cells:
                # Behandle die Absätze in jeder Zelle wie ein eigenes Mini-Dokument
                temp_doc_for_cell = type('obj', (object,), {'paragraphs': cell.paragraphs, 'tables': []})()
                docx_find_replace(temp_doc_for_cell, replacements)


def build_file_tree(root_path):
    """Durchsucht einen Ordner rekursiv und erstellt eine flache Liste für die Baumansicht."""
    file_tree = []
    root_level = root_path.count(os.sep)
    for dirpath, dirnames, filenames in os.walk(root_path):
        # Berechne die Tiefe für die Einrückung im Frontend
        level = dirpath.count(os.sep) - root_level
        # Füge das aktuelle Verzeichnis hinzu (außer dem Root-Verzeichnis selbst)
        if level >= 0:
            dir_name = os.path.basename(dirpath)
            if dir_name != root_path:  # Füge das Root-Verzeichnis nicht als eigenen Eintrag hinzu
                relative_dir_path = os.path.relpath(dirpath, root_path)
                file_tree.append({'name': dir_name, 'path': relative_dir_path, 'type': 'dir', 'depth': level})

            # Sortiere die Unterordner und Dateien für eine konsistente Anzeige
            dirnames.sort()
            filenames.sort()

            # Füge die Dateien im aktuellen Verzeichnis hinzu
            for f in filenames:
                relative_file_path = os.path.relpath(os.path.join(dirpath, f), root_path)
                file_tree.append({'name': f, 'path': relative_file_path, 'type': 'file', 'depth': level + 1})

    # Sortiere die gesamte Liste, um sicherzustellen, dass Ordner vor Dateien im selben Level kommen
    file_tree.sort(key=lambda x: (x['path'].split(os.sep), x['type'] == 'file'))
    return file_tree


def load_process_templates():
    """
    Lädt die Prozess-Templates aus der JSON-Datei.
    Erstellt die Datei mit einem Standard-Template, falls sie nicht existiert.
    """
    # Ensure db/process/ and template_images/ exist
    os.makedirs(PROCESS_TEMPLATE_IMAGES_DIR, exist_ok=True)
    os.makedirs(PROJECT_MANUALS_DIR, exist_ok=True)
    if not os.path.exists(MANUAL_CATALOG_FILE_PATH):
        with open(MANUAL_CATALOG_FILE_PATH, 'w', encoding='utf-8') as f:
            json.dump([], f, indent=2, ensure_ascii=False)

    # One-time migration: move legacy db/process_templates.json into db/process/
    legacy_path = 'db/process_templates.json'
    if not os.path.exists(PROCESS_TEMPLATES_FILE_PATH) and os.path.exists(legacy_path):
        import shutil
        shutil.move(legacy_path, PROCESS_TEMPLATES_FILE_PATH)
        print(f"INFO: process_templates.json migriert von '{legacy_path}' nach '{PROCESS_TEMPLATES_FILE_PATH}'.")

    if not os.path.exists(PROCESS_TEMPLATES_FILE_PATH):
        print(f"WARNUNG: '{PROCESS_TEMPLATES_FILE_PATH}' nicht gefunden. Erstelle eine Standard-Konfigurationsdatei.")
        default_templates = {
            "default": {
                "steps": [
                    {"name": "Rigging", "document_template": None},
                    {"name": "Test", "document_template": None},
                    {"name": "Paperwork", "document_template": None},
                    {"name": "De-Rigging", "document_template": None},
                    {"name": "After Test Work", "document_template": None},
                    {"name": "BSI", "document_template": None}
                ]
            }
        }
        try:
            with open(PROCESS_TEMPLATES_FILE_PATH, 'w', encoding='utf-8') as f:
                json.dump(default_templates, f, indent=2, ensure_ascii=False)
            print(f"INFO: Standard-Template-Datei '{PROCESS_TEMPLATES_FILE_PATH}' wurde erstellt.")
            return default_templates
        except IOError as e:
            print(f"FEHLER beim Erstellen der Template-Datei: {e}")
            return default_templates  # Gibt im Fehlerfall immer noch den Default-Wert zurück

    try:
        with open(PROCESS_TEMPLATES_FILE_PATH, 'r', encoding='utf-8') as f:
            return json.load(f)
    except (IOError, json.JSONDecodeError) as e:
        print(f"FEHLER beim Laden von process_templates.json: {e}")
        # Wichtiger Fallback, damit die App nicht abstürzt
        return {"default": {"steps": []}}


def find_best_template_for_engine(engine_type, templates):
    """
    Findet das passendste Prozess-Template für einen gegebenen Triebwerkstyp.
    Priorität: 1. Exakte Übereinstimmung -> 2. Längste Präfix-Übereinstimmung -> 3. Default.
    """
    if not engine_type or not templates:
        return templates.get('default')

    # 1. Höchste Priorität: Exakte Übereinstimmung
    if engine_type in templates:
        return templates[engine_type]

    # 2. Zweite Priorität: Längste Präfix-Übereinstimmung
    matching_keys = [key for key in templates.keys() if engine_type.startswith(key) and key != 'default']
    if matching_keys:
        # Finde den längsten (spezifischsten) Schlüssel unter den Treffern
        best_match_key = max(matching_keys, key=len)
        return templates[best_match_key]

    # 3. Letzte Priorität: Fallback auf das Default-Template
    return templates.get('default')


def calculate_dynamic_status(status: str, start_date_str: str, end_date_str: str) -> str:
    """
    Prüft, ob ein Training mit Status 'TS' (Gestartet) basierend auf dem Startdatum
    älter als ein Jahr ist und noch kein Abschlussdatum hat. Gibt dann 'TT' (Überfällig) zurück.
    Ein Status 'X' (Abgeschlossen) wird immer respektiert und nie geändert.
    """
    # 1. Wenn der Status bereits 'Abgeschlossen' ist oder kein Startdatum hat, nichts tun.
    #    Auch alle anderen Status ('TP', 'TT' etc.) werden nicht angefasst.
    if status != 'TS' or not start_date_str:
        return status

    # 2. Wenn bereits ein Abschlussdatum gesetzt ist, gilt das Training als abgeschlossen,
    #    auch wenn der Status noch nicht 'X' ist. In diesem Fall nichts ändern.
    if end_date_str:
        return status

    # 3. Parsen des Startdatums. Bei Fehler Originalstatus zurückgeben.
    try:
        start_date = parse_date_from_string(start_date_str)
        if not start_date:
            return status
    except (ValueError, TypeError):
        return status

    # 4. Berechne das "Ablaufdatum" (1 Jahr nach Start)
    expiry_date = start_date + timedelta(days=365)
    today = date.today()

    # 5. Wenn das heutige Datum nach dem Ablaufdatum liegt, ist es überfällig.
    if today > expiry_date:
        return 'TT'
    else:
        return status


def get_certificate_metadata(employee_id: str) -> List[Dict]:
    """Liest die Metadaten-JSON für einen Mitarbeiter (z.B. mmuster.json)."""
    os.makedirs(CERTIFICATES_FOLDER_PATH, exist_ok=True)  # Sicherstellen, dass der Hauptordner existiert
    metadata_path = os.path.join(CERTIFICATES_FOLDER_PATH, f"{secure_filename(employee_id)}.json")
    if os.path.exists(metadata_path):
        try:
            with open(metadata_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except (IOError, json.JSONDecodeError):
            return []
    return []


def save_certificate_metadata(employee_id: str, metadata: List[Dict]):
    """Schreibt die Metadaten-JSON für einen Mitarbeiter."""
    os.makedirs(CERTIFICATES_FOLDER_PATH, exist_ok=True)
    metadata_path = os.path.join(CERTIFICATES_FOLDER_PATH, f"{secure_filename(employee_id)}.json")
    try:
        with open(metadata_path, 'w', encoding='utf-8') as f:
            json.dump(metadata, f, indent=2, ensure_ascii=False)
    except IOError as e:
        print(f"FEHLER beim Schreiben der Metadaten für {employee_id}: {e}")


def get_aggregated_test_cell_data():
    """
    Sammelt alle Testzellen-Daten (manuell & operativ) aus allen Quellen.
    Dies ist die "Single Source of Truth" für den globalen Testzellen-Status.
    """
    # 1. Lade Testzellen-Stammdaten und manuelle Events
    locations_with_manual_events = load_locations_and_events()

    # Dependency Injection (Blocker übertragen)
    locations_with_manual_events = inject_dependency_blocks(locations_with_manual_events)
    if locations_with_manual_events is None:
        locations_with_manual_events = []

    all_test_cell_ids = [loc.get('ID') for loc in locations_with_manual_events if loc.get('ID')]

    # 2. Lade ALLE Projekte aus ALLEN konfigurierten Quellen
    all_projects = project_manager.get_all_active_projects()

    # 3. Bereite Projekte für die Berechnung vor
    import copy
    projects_for_calc = [copy.deepcopy(p) for p in all_projects if p]

    # --- FIX: ISO-Zeitstempel für den Algorithmus generieren ---
    for project in projects_for_calc:
        project_id_prefix = f"{project.get('project_code')}_{project.get('engine_serial')}"
        events_with_dt = []
        for i, evt in enumerate(project.get('events', [])):
            if not isinstance(evt, dict): continue

            if 'id' not in evt or not evt['id']:
                evt['id'] = f"{project_id_prefix}_{evt.get('title', 'event')}_{i}"

            start_dt = get_datetime_from_date_shift(parse_date_from_string(evt.get('start')), evt.get('start_shift'))
            end_dt = get_datetime_from_date_shift(parse_date_from_string(evt.get('end')), evt.get('end_shift'))

            if start_dt and end_dt:
                evt['start_datetime'] = start_dt.isoformat()
                evt['end_datetime'] = end_dt.isoformat()
                events_with_dt.append(evt)
        project['events'] = events_with_dt
    # -----------------------------------------------------------

    # 4. Führe eine Berechnung durch, um die Testzellen-Zuweisungen zu erhalten
    today = date.today()
    # 4 Wochen Historie für das schnelle Live-Fenster reichen aus, Rest kommt aus dem Archiv
    timeline_start_date = (today - timedelta(days=today.weekday())) - timedelta(weeks=4)
    num_days = 40 * 7

    calculated_data = calculate_timeline_assignments(
        projects=projects_for_calc, employees=[], qualifications=[], manual_assignments=[],
        timeline_start_date=timeline_start_date, num_days=num_days, assignment_rules=[],
        selected_test_cell_ids=all_test_cell_ids
    )

    # 5. Füge die berechnete Zuweisung zu den Projekt-Events hinzu
    assignments = calculated_data.get('assignments', {})
    for proj in projects_for_calc:
        for evt in proj.get('events', []):
            if evt.get('title', '').lower() == 'test':
                for i in range(100):
                    event_slot_key = f"{evt.get('id', '')}_{i}"
                    if event_slot_key in assignments:
                        assignment_info = assignments[event_slot_key]
                        if assignment_info and assignment_info.get("test_cell_id"):
                            evt['assigned_test_cell_id'] = assignment_info.get("test_cell_id")
                            break
                    else:
                        break

    # 6. Reichere die Daten mit den "Operational" Events an
    return aggregate_operational_events(locations_with_manual_events, projects_for_calc)


def order_system_setup_data_files():
    """Stellt sicher, dass das Datenverzeichnis und die JSON-Dateien für das Bestellsystem existieren."""
    if not os.path.exists(ORDER_DATA_DIR):
        os.makedirs(ORDER_DATA_DIR)

    if not os.path.exists(ORDER_ORDERS_FILE):
        initial_orders = [
            {"id": 1, "name": "Druckerpapier (A4)", "quantity": "5 Pakete", "requester": "Alice", "status": "requested",
             "createdAt": "2024-07-29T10:00:00.000Z", "notes": "Bitte extra dickes Papier (100g/m²).",
             "isCritical": True, "stdOrderNr": "STD-A-123", "supplier": "Office-Discount",
             "link": "https://www.office-discount.de/papier", "storageLocation": "Lager A, Regal 3"},
            {"id": 2, "name": "Kaffeebohnen", "quantity": "2 kg", "requester": "Bob", "status": "requested",
             "createdAt": "2024-07-28T14:30:00.000Z", "notes": "", "isCritical": False, "stdOrderNr": "",
             "supplier": "", "link": "", "storageLocation": "Küche, Schrank 1"},
            {"id": 6, "name": "Zucker", "quantity": "1 Kiste", "requester": "Charlie", "status": "completed",
             "createdAt": "2024-07-20T10:00:00.000Z", "notes": "", "isCritical": False, "orderedAt": "2024-07-21",
             "eta": "2024-07-25", "orderedBy": "Debug User", "deliveredAt": "2024-07-24", "batchNumber": "CH-123",
             "expiryDate": "2026-12-31", "stdOrderNr": "", "supplier": "", "link": "",
             "storageLocation": "Küche, Schrank 2", "documentationChecked": True,
             "documentationFile": "lieferschein_zucker.pdf"}
        ]
        with open(ORDER_ORDERS_FILE, 'w', encoding='utf-8') as f:
            json.dump(initial_orders, f, indent=4, ensure_ascii=False)

    if not os.path.exists(ORDER_TEMPLATES_FILE):
        initial_templates = [
            {"name": "Druckerpapier (A4)", "quantity": "10 Pakete", "notes": "Standard 80g/m²", "isCritical": False,
             "stdOrderNr": "STD-A-123", "supplier": "Office-Discount", "link": "https://www.office-discount.de/papier",
             "storageLocation": "Lager A, Regal 3"},
            {"name": "Kaffeebohnen (Espresso)", "quantity": "5 kg", "notes": "", "isCritical": False, "stdOrderNr": "",
             "supplier": "Kaffee-Rösterei", "link": "", "storageLocation": "Küche, Schrank 1"}
        ]
        with open(ORDER_TEMPLATES_FILE, 'w', encoding='utf-8') as f:
            json.dump(initial_templates, f, indent=4, ensure_ascii=False)


def order_system_load_data():
    """Lädt die Daten für das Bestellsystem aus den JSON-Dateien."""
    # Ruft die Setup-Funktion auf, um sicherzustellen, dass die Dateien existieren
    order_system_setup_data_files()
    with open(ORDER_ORDERS_FILE, 'r', encoding='utf-8') as f:
        orders = json.load(f)
    with open(ORDER_TEMPLATES_FILE, 'r', encoding='utf-8') as f:
        templates = json.load(f)
    return orders, templates


def order_system_save_data(data):
    """Speichert die übergebenen Daten in die entsprechenden JSON-Dateien des Bestellsystems."""
    if 'orders' in data:
        with open(ORDER_ORDERS_FILE, 'w', encoding='utf-8') as f:
            json.dump(data['orders'], f, indent=4, ensure_ascii=False)
    if 'templates' in data:
        with open(ORDER_TEMPLATES_FILE, 'w', encoding='utf-8') as f:
            json.dump(data['templates'], f, indent=4, ensure_ascii=False)


def load_maintenance_data():
    """
    Lädt Wartungsdaten aus den drei getrennten Dateien.
    Führt eine Migration durch, falls die neuen Dateien fehlen, aber die alte existiert.
    """
    data = {
        "orders": [],
        "definitions": [],
        "companies": [],
        "structure": []
    }

    # Helper zum Laden einer einzelnen Datei
    def load_json_file(filepath, default_val):
        if os.path.exists(filepath):
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except Exception as e:
                print(f"Fehler beim Laden von {filepath}: {e}")
        return default_val

    # Prüfen, ob wir migrieren müssen (wenn ORDERS Datei fehlt, aber ALTE Datei da ist)
    if not os.path.exists(MAINTENANCE_ORDERS_FILE) and os.path.exists(MAINTENANCE_DATA_FILE_OLD):
        print("INFO: Starte Migration von alter single-file zu multi-file Struktur...")
        try:
            with open(MAINTENANCE_DATA_FILE_OLD, 'r', encoding='utf-8') as f:
                old_data = json.load(f)

            # Daten verteilen
            data["orders"] = old_data.get("orders", [])
            data["definitions"] = old_data.get("definitions", [])
            data["companies"] = old_data.get("companies", [])  # Falls schon da

            # Sofort in neue Struktur speichern
            save_maintenance_orders(data["orders"])
            save_maintenance_definitions(data["definitions"])
            save_maintenance_companies(data["companies"])
            print("INFO: Migration erfolgreich abgeschlossen.")
            return data
        except Exception as e:
            print(f"FEHLER bei der Migration: {e}")

    # Normales Laden der getrennten Dateien
    data["orders"] = load_json_file(MAINTENANCE_ORDERS_FILE, [])
    data["definitions"] = load_json_file(MAINTENANCE_DEFINITIONS_FILE, [])
    data["companies"] = load_json_file(MAINTENANCE_COMPANIES_FILE, [])

    return data


def _load_json_file_unlocked(filepath, default_val=None):
    """Load JSON without acquiring the lock — caller must already hold file_write_lock."""
    if default_val is None:
        default_val = []
    if os.path.exists(filepath):
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"Fehler beim Laden von {filepath}: {e}")
    return default_val


def _write_json_file_unlocked(filepath, data):
    """Write JSON atomically without acquiring the lock — caller must already hold file_write_lock."""
    dir_name = os.path.dirname(filepath)
    unique_id = uuid.uuid4().hex
    temp_filepath = f"{filepath}.{unique_id}.tmp"
    try:
        os.makedirs(dir_name, exist_ok=True)
        with open(temp_filepath, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
            f.flush()
            os.fsync(f.fileno())
        os.replace(temp_filepath, filepath)
        return True
    except Exception as e:
        print(f"!!! KRITISCHER FEHLER beim Schreiben von {filepath}: {e}")
        traceback.print_exc()
        if os.path.exists(temp_filepath):
            try:
                os.remove(temp_filepath)
            except OSError:
                pass
        return False


def save_json_file(filepath, data):
    """Speichert Daten atomar und Thread-sicher (Race-Condition-Schutz)."""
    with _get_file_lock(filepath):
        return _write_json_file_unlocked(filepath, data)


def save_maintenance_orders(orders_data):
    # Wrapper, um sicherzustellen, dass immer die Atomic-Funktion genutzt wird
    return save_json_file(MAINTENANCE_ORDERS_FILE, orders_data)


def save_maintenance_definitions(definitions_data):
    # Wrapper, um sicherzustellen, dass immer die Atomic-Funktion genutzt wird
    return save_json_file(MAINTENANCE_DEFINITIONS_FILE, definitions_data)


def save_maintenance_companies(companies_data):
    # Wrapper, um sicherzustellen, dass immer die Atomic-Funktion genutzt wird
    return save_json_file(MAINTENANCE_COMPANIES_FILE, companies_data)


def sync_maintenance_links_to_hardware(maintenance_orders):
    """
    Aktualisiert Hardware-Daten (Verknüpfungen zu Wartungen & Issues).
    Nutzt Definitionen für aktuelle Titel.
    Unterstützt ID- und String-Matches.
    """
    try:
        te_data = load_test_equipment_data()
        if te_data is None: return

        inventory = te_data.get('inventory', {})
        today_iso = date.today().isoformat()

        # --- 0. DEFINITIONEN LADEN (Für aktuelle Titel) ---
        definitions_map = {}
        try:
            if os.path.exists(MAINTENANCE_DEFINITIONS_FILE):
                with open(MAINTENANCE_DEFINITIONS_FILE, 'r', encoding='utf-8') as f:
                    defs = json.load(f)
                    for d in defs:
                        if d.get('id'):
                            definitions_map[d['id']] = d.get('task', 'Unbenannter Plan')
        except Exception as e:
            print(f"Warnung beim Laden der Definitionen für Sync: {e}")

        # --- 1. DATEN VORBEREITEN ---
        id_to_sn_map = {}
        sn_to_id_map = {}

        for pn, part_data in inventory.items():
            for serial_entry in part_data.get('serials', []):
                hw_id = serial_entry.get('id')
                sn = serial_entry.get('sn')
                if hw_id:
                    id_to_sn_map[hw_id] = {'sn': sn, 'pn': pn}
                if sn:
                    sn_to_id_map[sn] = hw_id

                    # --- 2. ORDERS ANALYSIEREN ---
        asset_active_plans = {}
        asset_last_checks = {}
        asset_issues = {}
        asset_next_due = {}

        for order in maintenance_orders:
            target_asset_ids = set()

            # Assets sammeln (JSON & String Support)
            raw_assets = []
            if order.get('maintenanceObjects'):
                raw_assets.extend(order.get('maintenanceObjects'))
            elif order.get('maintenanceObject'):
                raw_assets.append(order.get('maintenanceObject'))
            if order.get('affectedAssets'):
                raw_assets.extend(order.get('affectedAssets'))
            elif order.get('affectedAsset'):
                raw_assets.append(order.get('affectedAsset'))

            for raw in raw_assets:
                if not raw: continue
                if raw.strip().startswith('{'):
                    try:
                        obj = json.loads(raw)
                        if obj.get('id'): target_asset_ids.add(obj.get('id'))
                    except:
                        pass
                else:
                    sn_part = raw.split(' ')[0]
                    if sn_part in sn_to_id_map:
                        target_asset_ids.add(sn_to_id_map[sn_part])

            link_id = order.get('parentTaskId') or order.get('id')
            status = order.get('status')
            order_date = order.get('date', '')

            for asset_id in target_asset_ids:
                if not asset_id: continue

                # ISSUE LOGIK
                if order.get('type') == 'I' and status != 'Completed':
                    if asset_id not in asset_issues: asset_issues[asset_id] = {'count': 0, 'critical': False,
                                                                               'list': []}
                    asset_issues[asset_id]['count'] += 1
                    asset_issues[asset_id]['list'].append(
                        {'id': order.get('id'), 'title': order.get('task'), 'priority': order.get('priority')})
                    if order.get('priority') == 'Critical' or order.get('isBlocker'): asset_issues[asset_id][
                        'critical'] = True

                # WARTUNGS LOGIK
                if order.get('type') == 'M' or not order.get('type'):
                    if status == 'Completed' and order.get('completedDate'):
                        if asset_id not in asset_last_checks: asset_last_checks[asset_id] = {}
                        curr = asset_last_checks[asset_id].get(link_id)
                        if not curr or order.get('completedDate') > curr:
                            asset_last_checks[asset_id][link_id] = order.get('completedDate')

                    if status != 'Completed' and status != 'Review Ready':
                        if asset_id not in asset_active_plans: asset_active_plans[asset_id] = set()
                        asset_active_plans[asset_id].add(link_id)

                        if order_date:
                            if asset_id not in asset_next_due:
                                asset_next_due[asset_id] = order_date
                            elif order_date < asset_next_due[asset_id]:
                                asset_next_due[asset_id] = order_date

        # --- 3. INVENTAR UPDATEN ---
        changes_made = False
        for pn, part_data in inventory.items():
            for serial_entry in part_data.get('serials', []):
                hw_id = serial_entry.get('id') or sn_to_id_map.get(serial_entry.get('sn'))
                if not hw_id: continue

                new_links_list = []
                if hw_id in asset_active_plans:
                    for plan_id in asset_active_plans[hw_id]:
                        last_date = "N/A"
                        if hw_id in asset_last_checks and plan_id in asset_last_checks[hw_id]:
                            last_date = asset_last_checks[hw_id][plan_id]

                        # --- TITEL LOGIK: Definition > Auftrag ---
                        plan_title = "Unbekannter Plan"
                        if plan_id in definitions_map:
                            plan_title = definitions_map[plan_id]
                        else:
                            # Fallback: Suche in Orders
                            found = next((o for o in maintenance_orders if o.get('id') == plan_id), None)
                            if found: plan_title = found.get('task', 'Unbekannt')

                        new_links_list.append({"id": plan_id, "title": plan_title, "lastCheck": last_date})

                new_links_list.sort(key=lambda x: x['id'])

                traffic_light = "GREEN"
                next_due = asset_next_due.get(hw_id)
                issues = asset_issues.get(hw_id)

                if issues and issues['critical']:
                    traffic_light = "RED"
                elif next_due and next_due < today_iso:
                    traffic_light = "AMBER"
                elif issues and issues['count'] > 0:
                    traffic_light = "YELLOW"
                elif next_due:
                    try:
                        if datetime.strptime(next_due, "%Y-%m-%d").date() <= (
                                date.today() + timedelta(days=14)): traffic_light = "BLUE"
                    except:
                        pass
                elif not new_links_list:
                    traffic_light = "NONE"

                new_status_summary = {
                    "light": traffic_light,
                    "nextDue": next_due or "N/A",
                    "openIssues": issues['count'] if issues else 0,
                    "issueList": issues['list'] if issues else []
                }

                current_links = serial_entry.get('maintenanceLinks', [])
                current_status = serial_entry.get('statusSummary', {})

                if (json.dumps(current_links, sort_keys=True) != json.dumps(new_links_list, sort_keys=True) or
                        json.dumps(current_status, sort_keys=True) != json.dumps(new_status_summary, sort_keys=True)):
                    serial_entry['maintenanceLinks'] = new_links_list
                    serial_entry['statusSummary'] = new_status_summary
                    changes_made = True

        if changes_made:
            save_test_equipment_data_to_file(te_data)
            print(f"INFO: Hardware-Daten (inkl. aktuellen Titeln) synchronisiert.")

    except Exception as e:
        print(f"FEHLER beim Synchronisieren: {e}")
        traceback.print_exc()



def get_flat_facility_units(nodes, prefix=""):
    """
    Rekursive Hilfsfunktion, die den Strukturbaum in eine flache Liste
    für Dropdowns umwandelt.
    Rückgabe: [{'id': 'uuid...', 'name': '1. Halle Nord', 'path': '0'}, ...]
    """
    result = []
    for i, node in enumerate(nodes):
        # Index für die Anzeige generieren (z.B. "1.1")
        current_index_str = f"{prefix}{i + 1}"
        display_name = f"{current_index_str} {node.get('name', 'Unbekannt')}"

        result.append({
            'id': node.get('id'),
            'name': display_name,
            'original_name': node.get('name'),
            'type': 'unit'  # Marker, falls wir später Gebäude unterscheiden wollen
        })

        if 'children' in node:
            result.extend(get_flat_facility_units(node['children'], current_index_str + "."))

    return result


def init_maintenance_structure():
    """Erstellt die Ordnerstruktur für Maintenance."""
    if not os.path.exists(MAINTENANCE_DATA_DIR):
        os.makedirs(MAINTENANCE_DATA_DIR)
        print(f"INFO: Ordner {MAINTENANCE_DATA_DIR} erstellt.")

    if not os.path.exists(MAINTENANCE_UPLOADS_DIR):
        os.makedirs(MAINTENANCE_UPLOADS_DIR)
        print(f"INFO: Ordner {MAINTENANCE_UPLOADS_DIR} erstellt.")

    if not os.path.exists(MAINTENANCE_LOGBOOK_UPLOADS_DIR):
        os.makedirs(MAINTENANCE_LOGBOOK_UPLOADS_DIR)
        print(f"INFO: Ordner {MAINTENANCE_LOGBOOK_UPLOADS_DIR} erstellt.")


# --- NEU: ProjectManager Klasse ---
class ProjectManager:
    def __init__(self):
        self.projects_cache = []
        self.timeline_file_map = {}  # Map: timeline_id -> file_path
        self.reload_config()

    def reload_config(self):
        """Lädt die Konfiguration und baut die Maps auf."""
        config = load_app_config()
        self.timeline_file_map = {}
        all_files = set()

        for tl in config.get('timelines', []):
            # Editable Timelines haben EINE Datei
            if tl.get('type') == 'editable' and tl.get('data_file'):
                self.timeline_file_map[tl['id']] = [tl['data_file']]
                all_files.add(tl['data_file'])

            # Statische Timelines können MEHRERE Dateien haben
            if tl.get('type') == 'static' and tl.get('data_files'):
                self.timeline_file_map[tl['id']] = tl['data_files']
                for f in tl['data_files']:
                    all_files.add(f)

        # Lade alle Daten einmalig in den Cache
        self.load_all_files(list(all_files))

    def load_all_files(self, file_paths):
        """Lädt alle Projekte aus den Dateien in den Cache."""
        self.projects_cache = load_timeline_projects_for_flask(file_paths)
        print(f"ProjectManager: {len(self.projects_cache)} Projekte global geladen.")

    def get_all_active_projects(self):
        """Liefert alle aktiven Projekte systemweit."""
        return [p for p in self.projects_cache if p.get('active')]

    def get_projects_for_timeline(self, timeline_id):
        """Filtert Projekte für eine spezifische Timeline."""
        # 1. Welche Dateien gehören zu dieser Timeline?
        files = self.timeline_file_map.get(timeline_id, [])
        if not files: return []

        # ACHTUNG: load_timeline_projects_for_flask lädt aktuell vom Dateisystem.
        # Um den Cache zu nutzen, müssten wir wissen, aus welcher Datei ein Projekt kam.
        # Da unsere aktuelle load-Funktion das nicht speichert (sie merged alles),
        # müssen wir für diesen Schritt pragmatisch sein:
        # Wir laden für die Timeline-Ansicht (noch) direkt, um Konsistenz zu den Filtern zu wahren.

        # OPTIMIERUNG:
        # Langfristig sollte load_timeline_projects jedem Projekt ein Feld 'source_file' geben.
        # Dann könnten wir hier einfach filtern:
        # return [p for p in self.projects_cache if p['source_file'] in files]

        return load_timeline_projects_for_flask(files)

    def save_projects(self, projects, timeline_id):
        """Speichert Projekte in die Datei der Timeline (nur für Editable)."""
        files = self.timeline_file_map.get(timeline_id)
        if not files or len(files) > 1:
            print(f"Fehler: Speichern für Timeline {timeline_id} nicht möglich (Keine oder mehrere Dateien).")
            return False

        target_file = files[0]
        if save_projects_for_flask(target_file, projects):
            # Cache aktualisieren (einfachste Methode: Alles neu laden)
            self.reload_config()
            return True
        return False

# Globale Instanz
project_manager = ProjectManager()

class GlobalAssignmentManager:
    def __init__(self):
        self.assignments = {}
        self.load()

    def load(self):
        if os.path.exists(GLOBAL_ASSIGNMENTS_FILE):
            try:
                with open(GLOBAL_ASSIGNMENTS_FILE, 'r', encoding='utf-8') as f:
                    self.assignments = json.load(f)
            except:
                self.assignments = {}

    def save(self):
        with open(GLOBAL_ASSIGNMENTS_FILE, 'w', encoding='utf-8') as f:
            json.dump(self.assignments, f, indent=2)

    def set_assignment(self, date_iso, shift, user_id, details):
        print(f"DEBUG SET: {date_iso} {shift} {user_id}")
        print(f"DEBUG INSTANCE ID: {id(self)}")
        if date_iso not in self.assignments: self.assignments[date_iso] = {}
        if shift not in self.assignments[date_iso]: self.assignments[date_iso][shift] = {}

        # Conflict Check könnte hier passieren
        self.assignments[date_iso][shift][user_id] = details

    def clear_assignments_for_date(self, date_iso):
        if date_iso in self.assignments:
            del self.assignments[date_iso]

    def get_assignments_for_day(self, date_iso):
        return self.assignments.get(date_iso, {})

    def get_busy_employees_in_range(self, start_date, end_date, ignore_timeline_id=None):
        busy_map = {}  # Key: (date_iso, shift), Value: Set of user_ids

        current = start_date
        while current <= end_date:
            date_iso = current.isoformat()
            if date_iso in self.assignments:
                for shift, users in self.assignments[date_iso].items():
                    key = (date_iso, shift)
                    if key not in busy_map: busy_map[key] = set()

                    for uid, details in users.items():
                        # WICHTIG: Ignoriere Einträge der eigenen Timeline!
                        if ignore_timeline_id and details.get('timeline') == ignore_timeline_id:
                            continue
                        busy_map[key].add(uid)
            current += timedelta(days=1)
        return busy_map

    def load_history(self):
        if os.path.exists(ASSIGNMENT_HISTORY_FILE):
            try:
                with open(ASSIGNMENT_HISTORY_FILE, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                return {}
        return {}

    def save_history(self, history_data):
        with open(ASSIGNMENT_HISTORY_FILE, 'w', encoding='utf-8') as f:
            json.dump(history_data, f, indent=2)

    def archive_old_entries(self):
        """Verschiebt alles älter als 5 Tage ins Archiv."""
        today = date.today()
        cutoff_date = today - timedelta(days=5)

        history = self.load_history()
        keys_to_delete = []

        for date_iso, shifts in self.assignments.items():
            if date_iso < cutoff_date.isoformat():
                # Ins Archiv kopieren
                history[date_iso] = shifts
                keys_to_delete.append(date_iso)

        # Aus Live löschen
        for k in keys_to_delete:
            del self.assignments[k]

        if keys_to_delete:
            self.save_history(history)
            self.save()
            print(f"INFO: {len(keys_to_delete)} Tage ins Archiv verschoben.")

# Globale Instanz
global_scheduler = GlobalAssignmentManager()


def load_testcell_history():
    if os.path.exists(TESTCELL_HISTORY_FILE):
        try:
            with open(TESTCELL_HISTORY_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except:
            pass
    return {}


def save_testcell_history(data):
    return save_json_file(TESTCELL_HISTORY_FILE, data)


def archive_old_testcell_events():
    """
    Zieht einen Snapshot der berechneten Testzellen-Auslastung und
    archiviert alles, was älter als 5 Tage ist. Löscht alte manuelle Events aus der CSV.
    """
    print("--- Starte Testzellen Archivierung ---")
    cutoff_date = date.today() - timedelta(days=5)
    cutoff_iso = cutoff_date.isoformat()

    history = load_testcell_history()

    # 1. Snapshot der "Allwissenden Ansicht" holen
    all_data = get_aggregated_test_cell_data()

    archived_count = 0
    for cell in all_data:
        for event in cell.get('events', []):
            # Nur archivieren, wenn das Event beendet ist und das Ende vor dem Cutoff liegt
            if event.get('end_date', '') < cutoff_iso:
                event_id = event.get('event_id')
                if event_id:
                    # Wir nutzen ein Dictionary (Key=Event-ID), das verhindert Duplikate.
                    # Bereits archivierte Events werden ggf. mit ihrem finalen Status geupdatet.
                    history[event_id] = event
                    archived_count += 1

    save_testcell_history(history)
    print(f"INFO: {archived_count} Events im Archiv aktualisiert/gespeichert.")

    # 2. Aufräumen der manuellen CSV (Nur Zukunft & letzte 5 Tage behalten)
    if os.path.exists(LOCATIONS_EVENTS_CSV_PATH):
        try:
            manual_events = []
            with open(LOCATIONS_EVENTS_CSV_PATH, mode='r', newline='', encoding='utf-8-sig') as f:
                reader = csv.DictReader(f)
                for row in reader:
                    # Wir behalten nur Events, die NACH dem Cutoff enden
                    if row.get('end_date', '') >= cutoff_iso:
                        manual_events.append(row)

            # CSV überschreiben
            event_headers = ['event_id', 'cell_id', 'title', 'type', 'start_date', 'start_shift', 'end_date',
                             'end_shift', 'description', 'created_by', 'created_at', 'last_modified_by',
                             'last_modified_at']
            with open(LOCATIONS_EVENTS_CSV_PATH, mode='w', newline='', encoding='utf-8-sig') as f:
                writer = csv.DictWriter(f, fieldnames=event_headers)
                writer.writeheader()
                writer.writerows(manual_events)
            print(f"INFO: Testzellen CSV aufgeräumt. Es verbleiben {len(manual_events)} aktive manuelle Einträge.")
        except Exception as e:
            print(f"Fehler beim Aufräumen der Testzellen CSV: {e}")

def update_global_schedule():
    """Berechnet die globale Belegung neu (Zukunft) und archiviert Altes."""
    print("--- Start Global Schedule Update ---")
    global global_scheduler
    # 1. Laden & Archivieren (Cleanup)
    # Das verschiebt alles < (Heute - 5 Tage) in die History
    global_scheduler.load()
    global_scheduler.archive_old_entries()

    # 2. Reset NUR der Zukunft (ab heute)
    # Wir löschen alle Einträge ab heute, damit sie frisch berechnet werden können.
    # Die manuellen Korrekturen der letzten 5 Tage bleiben erhalten.
    today = date.today()
    today_iso = today.isoformat()

    # NEU: Wir speichern ein "Gedächtnis" der Zukunft, bevor wir sie löschen
    previous_future_assignments = {d: v for d, v in global_scheduler.assignments.items() if d >= today_iso}

    future_dates =[d for d in list(global_scheduler.assignments.keys()) if d >= today_iso]
    for d in future_dates:
        del global_scheduler.assignments[d]

    # 3. Globale Daten laden
    if not ALL_EMPLOYEES: load_and_prepare_qualifications_data()
    if not ALL_QUALIFICATIONS: load_and_prepare_qualifications_data()

    config = load_app_config()

    # Startdatum für die Berechnung ist HEUTE (da wir die Vergangenheit ja behalten haben)
    start_date = today
    num_days = 365 * 2  # 2 Jahre Vorschau

    # 4. Berechnung (Zukunft)
    for tl in config.get('timelines', []):
        if tl.get('type') != 'editable': continue

        tl_id = tl['id']
        projects = project_manager.get_projects_for_timeline(tl_id)

        # Datenaufbereitung (ISO-Dates)
        for project in projects:
            project_id_prefix = f"{project.get('project_code')}_{project.get('engine_serial')}"
            prepared_events = []
            for i, evt_data in enumerate(project.get('events', [])):
                if not isinstance(evt_data, dict): continue
                if 'id' not in evt_data or not evt_data['id']:
                    evt_data['id'] = f"{project_id_prefix}_{evt_data.get('title', 'event')}_{i}"

                start_dt = get_datetime_from_date_shift(parse_date_from_string(evt_data.get('start')),
                                                        evt_data.get('start_shift'))
                end_dt = get_datetime_from_date_shift(parse_date_from_string(evt_data.get('end')),
                                                      evt_data.get('end_shift'))

                if start_dt and end_dt:
                    evt_data['start_datetime'] = start_dt.isoformat()
                    evt_data['end_datetime'] = end_dt.isoformat()
                    prepared_events.append(evt_data)
            project['events'] = prepared_events

        groups = tl.get('assignment_groups', [])
        manual_file = tl.get('manual_assignments_file')
        manual_assignments = load_assigned_employees(manual_file) if manual_file else []
        rules_file = tl.get('assignment_rules_file')
        rules = load_assignment_rules(rules_file) if rules_file else []

        # Berechnung aufrufen
        result = calculate_timeline_assignments(
            projects, ALL_EMPLOYEES, ALL_QUALIFICATIONS, manual_assignments,
            start_date, num_days, rules, groups,
            previous_global_assignments=previous_future_assignments
        )

        assignments = result.get('assignments', {})
        print(f"DEBUG: Timeline {tl_id} hat {len(assignments)} Slots mit Daten (Zukunft).")

        # Ergebnisse speichern
        for event_slot_key, data in assignments.items():
            date_iso = data.get('date')
            shift = data.get('shift')
            employees = data.get('employees', [])

            # --- DEBUG BLOCK ---
            if employees:
                print(f"DEBUG CANDIDATE: {date_iso} {shift} -> {employees}")
            # -------------------

            if not date_iso or not shift: continue

            # WICHTIG: Nur speichern, wenn es >= Heute ist (Sicherheitsnetz)
            if date_iso < today_iso: continue

            print(f"DEBUG ADDING {date_iso}")
            for emp_id in employees:
                global_scheduler.set_assignment(date_iso, shift, emp_id, {
                    "timeline": tl_id,
                    "project": data.get('project_name'),
                    "engine": data.get('engine'),
                    "task": data.get('task'),
                    "type": "manual" if data.get('is_manual') else "auto"
                })


    # 5. Speichern
    print(f"DEBUG TOTAL: Scheduler hat jetzt {len(global_scheduler.assignments)} Tage im Speicher.")
    print(f"DEBUG TOTAL INSTANCE ID: {id(global_scheduler)}")
    global_scheduler.save()

    try:
        archive_old_testcell_events()
    except Exception as e:
        print(f"Fehler bei Testzellen-Archivierung: {e}")

def normalize_engine_family(engine_name):
    """Fasst spezifische Typen zu Familien zusammen."""
    if not engine_name: return "Unknown"
    u = engine_name.upper().strip()

    # Priorität: Längere Matches zuerst prüfen!
    if "CF34-10" in u: return "CF34-10"
    if "CF34-8" in u: return "CF34-8"
    if "CFM56-7B" in u: return "CFM56-7B"

    if "PW2" in u: return "PW200"
    if "PW3" in u: return "PW300"
    if "PW5" in u: return "PW500"
    if "PW8" in u: return "PW800"
    if "LEAP" in u: return "LEAP"

    if "LM25" in u: return "LM2500"
    if "LM6" in u: return "LM6000"

    # Fallback für alles andere
    return u


class SmartScheduler:
    def __init__(self, history_data, global_assignments):
        self.history = history_data
        self.live = global_assignments
        self.relations = {}
        self.load_relations()

    def load_relations(self):
        """Lädt Buddy- und Team-Daten."""
        if os.path.exists(USER_RELATIONS_FILE):
            try:
                with open(USER_RELATIONS_FILE, 'r', encoding='utf-8') as f:
                    self.relations = json.load(f)
            except:
                self.relations = {}

    def get_user_preferences(self, user_id):
        if not user_id: return {"buddy": None, "preferred_partners": []}
        user_rel = self.relations.get(user_id, {})
        buddy = user_rel.get('buddy')
        if not buddy: buddy = None
        groups = user_rel.get('groups', [])
        partners = set()
        for other_id, other_rel in self.relations.items():
            if other_id == user_id: continue
            other_groups = other_rel.get('groups', [])
            if any(g in other_groups for g in groups):
                partners.add(other_id)
        return {
            "buddy": buddy,
            "preferred_partners": list(partners)
        }

    def get_last_experience_date(self, user_id, engine_type, task_name, today_iso):
        """Sucht das letzte Datum für User + Engine + Task (NUR IN DER VERGANGENHEIT)."""
        last_date_str = "1900-01-01"

        def search_in_dict(data_dict):
            nonlocal last_date_str
            for date_iso, shifts in data_dict.items():
                # STRIKTE TRENNUNG: Zukunft zählt nicht als Erfahrung!
                if date_iso >= today_iso: continue

                if date_iso <= last_date_str: continue
                for shift, users in shifts.items():
                    if user_id in users:
                        details = users[user_id]
                        eng_match = details.get('engine') and engine_type and engine_type in details['engine']
                        task_match = False
                        if task_name and details.get('task'):
                            t1 = task_name.upper().strip()
                            t2 = details.get('task').upper().strip()
                            if t1 == t2 or t1 in t2:
                                task_match = True

                        if eng_match and task_match:
                            last_date_str = date_iso

        search_in_dict(self.history)
        search_in_dict(self.live)

        return datetime.strptime(last_date_str, "%Y-%m-%d").date()

    def worked_on_project_yesterday(self, user_id, context, today_iso):
        """Prüft Kontinuität (NUR IN DER VERGANGENHEIT)."""
        today = context['date_obj']
        yesterday = (today - timedelta(days=1)).isoformat()

        # STRIKTE TRENNUNG: Wenn "Gestern" eigentlich in der Zukunft liegt, zählt es nicht!
        if yesterday >= today_iso:
            return False

        data_source = self.live if yesterday in self.live else self.history
        if yesterday in data_source:
            for shift in ['AM', 'PM']:
                if user_id in data_source[yesterday].get(shift, {}):
                    details = data_source[yesterday][shift][user_id]
                    if details.get('project') and context['project_code'] in details['project']:
                        return True
        return False

    def calculate_score(self, candidate, context, current_team):
        score = 0
        reasons = []
        user_id = candidate['id'].lower()
        today_iso = date.today().isoformat()

        # --- A. INDIVIDUUM ---
        if context['quali_status'] == 'X':
            score += 100
        else:
            score += 50

        if context['engine'] and context['task']:
            last_date = self.get_last_experience_date(user_id, context['engine'], context['task'], today_iso)
            days_since = (context['date_obj'] - last_date).days
            if days_since > 0:
                boost = min(days_since, 180)
                score += boost

        if self.worked_on_project_yesterday(user_id, context, today_iso):
            score += 200
            reasons.append("Kontinuität")

        # --- B. TEAM-FIT ---
        if current_team:
            prefs = self.get_user_preferences(user_id)

            for teammate in current_team:
                t_prefs = self.get_user_preferences(teammate['id'])
                is_buddy = (prefs['buddy'] == teammate['id'] or t_prefs['buddy'] == user_id)

                if is_buddy:
                    cand_status = context['quali_status']
                    mate_status = teammate['quali_status']
                    is_mentoring_case = (cand_status == 'X' and mate_status != 'X') or (
                                cand_status != 'X' and mate_status == 'X')

                    if is_mentoring_case:
                        score += 500
                        reasons.append(f"Mentoring Buddy mit {teammate['name']} (+500)")
                    else:
                        score += 50
                        reasons.append(f"Buddy (Sozial) mit {teammate['name']} (+50)")

                elif teammate['id'] in prefs['preferred_partners'] or user_id in t_prefs['preferred_partners']:
                    score += 150
                    reasons.append(f"Team-Präferenz mit {teammate['name']} (+150)")

            team_has_expert = any(t['quali_status'] == 'X' for t in current_team)
            team_has_trainee = any(t['quali_status'] != 'X' for t in current_team)

            if context['quali_status'] == 'X':
                if team_has_trainee and not team_has_expert:
                    score += 300
                    reasons.append("Supervisor Bonus")
                elif team_has_expert:
                    score -= 50
            else:
                if team_has_expert:
                    score += 300
                    reasons.append("Learning Bonus")
                elif team_has_trainee:
                    score -= 100
                    reasons.append("Trainee-Cluster Malus")

        return score


def can_user_review(order, user_id, user_roles):
    user = next((u for u in ALL_USERS if u['id'] == user_id), None)
    user_units = user.get('units', []) if user else []

    # 1. Admin darf immer
    if Roles.ADMIN in user_roles: return True

    # 2. Designated Reviewer
    designated = order.get('designatedReviewer', '').strip().lower()
    if designated and designated == user_id: return True

    if user_units:
        target_unit = order.get('plantIndex', '')
        # Wenn Auftrag keinen Ort hat, aber User eingeschränkt ist -> Darf er das sehen?
        # Diskussion: Ein "Allgemeines" Issue sollte vielleicht jeder sehen?
        # Entscheidung: Ja, ohne Ort -> Zeigen.
        if target_unit:
            # plantIndex ist als "{index} {name}" gespeichert (z.B. "4 Prüfstand 4")
            # Nur den Index-Prefix extrahieren für den Vergleich mit user_units
            target_index = target_unit.split(' ')[0]
            is_responsible = False
            for u in user_units:
                if u == target_index or target_index.startswith(u + "."):
                    is_responsible = True
                    break
            if not is_responsible:
                return False # Harter Abbruch: Falscher Ort
    # -----------------------

    # 3. Topic Berechtigung (exakte Übereinstimmung mit GLOBAL_AREAS-Werten)
    topic = (order.get('topic') or order.get('category') or '').strip().lower()

    if topic == 'elektrik / messtechnik':
        return Roles.TOPIC_ELEKTRIK in user_roles

    if topic == 'anlagen / mechanik':
        return Roles.TOPIC_MECHANIK in user_roles

    if topic == 'it / software':
        return Roles.TOPIC_IT in user_roles

    if topic == 'facility management':
        return Roles.TOPIC_FACILITY in user_roles

    if topic == 'test engineering':
        return Roles.TOPIC_TESTENG in user_roles

    # 4. Fallback Rollen
    if Roles.TESTCELLINSPECTOR in user_roles or Roles.MAINTENANCEMANAGER in user_roles:
        return True

    return False


def ensure_kit_ids_and_migration(data):
    """
    Migriert alte Kit-Strukturen (Key = Name) zu UUIDs.
    Aktualisiert Mappings und Inventar-Referenzen.
    """
    boms = data.get('boms', {})
    mappings = data.get('mappings', {})
    inventory = data.get('inventory', {})

    # Map AlterName -> NeueUUID für Referenz-Updates
    name_to_uuid_map = {}
    new_boms = {}
    changes_made = False

    # 1. BOMS durchgehen und UUIDs vergeben
    for key, kit_data in boms.items():
        # Prüfen, ob der Key schon eine UUID ist (einfacher Check auf Prefix oder Länge)
        # Wir nehmen an, alte Namen sind keine UUIDs. Wir generieren neue IDs mit Prefix 'kit_'

        if key.startswith('kit_') and 'id' in kit_data and kit_data['id'] == key:
            # Schon migriert
            new_boms[key] = kit_data
            # Mapping für Sicherheit auch hier pflegen (falls Namen in Mappings gemischt sind)
            name_to_uuid_map[kit_data.get('name')] = key
        else:
            # Das ist ein alter Eintrag (Key = Name)
            new_uuid = f"kit_{uuid.uuid4().hex[:8]}"

            # Neues Objekt bauen
            new_kit_obj = {
                "id": new_uuid,
                "name": key,  # Der alte Key war der Name
                "items": kit_data.get('items', []),
                "status": kit_data.get('status', 'missing')
            }
            new_boms[new_uuid] = new_kit_obj
            name_to_uuid_map[key] = new_uuid
            changes_made = True
            print(f"MIGRATION: Kit '{key}' zu UUID '{new_uuid}' konvertiert.")

    data['boms'] = new_boms

    # 2. Mappings aktualisieren (Engine -> [KitUUIDs])
    if changes_made:
        for engine, kit_list in mappings.items():
            new_kit_list = []
            for kit_ref in kit_list:
                # Wenn die Referenz ein alter Name ist, ersetze durch UUID
                if kit_ref in name_to_uuid_map:
                    new_kit_list.append(name_to_uuid_map[kit_ref])
                elif kit_ref in new_boms:  # Schon eine UUID
                    new_kit_list.append(kit_ref)
                else:
                    # Referenz existiert nicht mehr in BOMs -> entfernen oder behalten?
                    # Wir behalten sie vorerst nicht, um Datenmüll zu vermeiden
                    print(f"WARNUNG: Kit-Referenz '{kit_ref}' in Engine '{engine}' nicht gefunden.")

            mappings[engine] = new_kit_list

        # 3. Inventar aktualisieren (Serial -> kitId)
        for pn, part in inventory.items():
            for serial in part.get('serials', []):
                old_kit_ref = serial.get('kitId', 'N/A')
                if old_kit_ref != 'N/A' and old_kit_ref in name_to_uuid_map:
                    serial['kitId'] = name_to_uuid_map[old_kit_ref]

    return changes_made


def recalculate_kit_status(data):
    """
    Berechnet serverseitig, ob Kits vollständig sind.
    Setzt 'status' im Kit und 'kitId' im Inventar.
    """
    inventory = data.get('inventory', {})
    boms = data.get('boms', {})

    print("--- DEBUG: Starte Kit-Neuberechnung (Python) ---")

    # 1. Reset aller Zuweisungen im Inventar
    # Wir setzen alles auf 'N/A', es sei denn, der Status ist Maintenance/Decommissioned
    # (dann ist das Teil eh nicht verfügbar).
    pool = {}  # Map: PN -> Liste verfügbarer SNs (Objekte)

    for pn, part in inventory.items():
        pool[pn] = []
        for serial in part.get('serials', []):
            # Reset Kit-Zuweisung
            serial['kitId'] = 'N/A'

            # Nur 'Available' Teile kommen in den Pool
            if serial.get('status') == 'Available':
                pool[pn].append(serial)

    # 2. Kits durchgehen und Material "buchen"
    # Wir sortieren Kits vielleicht nach Name, damit die Zuweisung deterministisch ist
    sorted_kit_ids = sorted(boms.keys(), key=lambda k: boms[k].get('name', ''))

    for kit_id in sorted_kit_ids:
        kit = boms[kit_id]
        items_needed = kit.get('items', [])

        if not items_needed:
            kit['status'] = 'missing'
            continue

        is_fully_ready = True
        is_partial = False

        for item in items_needed:
            needed_pn = item.get('partNumber')
            needed_qty = int(item.get('quantity', 1))

            available_sns = pool.get(needed_pn, [])

            # Checken wie viele wir haben
            if len(available_sns) >= needed_qty:
                # Genug da -> Zuweisen
                # Wir nehmen die ersten X aus dem Pool und entfernen sie
                for _ in range(needed_qty):
                    sn_obj = available_sns.pop(0)
                    sn_obj['kitId'] = kit_id  # HIER wird die UUID zugewiesen
                    # sn_obj['status'] = 'Assigned' # OPTIONAL: Wenn du willst, dass der Status sich ändert

                is_partial = True  # Zumindest dieser Teil ist da

            elif len(available_sns) > 0:
                # Teilweise da -> Alles nehmen was da ist
                for _ in range(len(available_sns)):
                    sn_obj = available_sns.pop(0)
                    sn_obj['kitId'] = kit_id

                is_partial = True
                is_fully_ready = False
            else:
                # Gar nichts da
                is_fully_ready = False

        # Status setzen
        if is_fully_ready:
            kit['status'] = 'ready'
        elif is_partial:
            kit['status'] = 'partial'
        else:
            kit['status'] = 'missing'

    print("--- DEBUG: Kit-Neuberechnung abgeschlossen ---")
    return True  # Immer True, da wir Daten modifiziert haben

# 4. Flask Routen

@app.route('/api/analytics/test_cells', methods=['GET'])
@login_required
def api_analytics_test_cells():
    # 1. Live Daten holen (die letzten 4 Wochen + Zukunft)
    live_cells = get_aggregated_test_cell_data()

    # 2. History holen
    history_events = list(load_testcell_history().values())

    # 3. Mergen (Zusammenführen)
    cell_map = {c['ID']: c.copy() for c in live_cells}

    # Live-Event-IDs sammeln (um Duplikate im Überschneidungsbereich zu verhindern)
    live_event_ids = set()
    for c in live_cells:
        for e in c.get('events', []):
            if e.get('event_id'): live_event_ids.add(e.get('event_id'))

    # Archivierte Events anhängen, falls sie nicht im Live-Set sind
    for he in history_events:
        cell_id = he.get('cell_id')
        event_id = he.get('event_id')

        if cell_id in cell_map and event_id not in live_event_ids:
            he_copy = he.copy()
            he_copy['isArchived'] = True  # <--- WICHTIG: Flag setzen!
            cell_map[cell_id]['events'].append(he_copy)

    return jsonify(list(cell_map.values()))

def _count_deviations(data):
    total, open_count = 0, 0
    def walk(steps):
        nonlocal total, open_count
        for s in steps:
            for d in s.get('deviations', []):
                total += 1
                if d.get('status') == 'Open':
                    open_count += 1
            walk(s.get('children', []))
    walk(data.get('steps', []))
    return total, open_count

@app.route('/api/archive/projects/<string:timeline_id>')
@login_required
def api_get_archived_projects(timeline_id):
    """
    Scant den Status-Ordner (inkl. archiv/ Unterordner) und gibt eine Liste der Projekte zurück,
    die explizit zu der angefragten Timeline gehören.
    """
    archived_projects = []
    if not os.path.exists(PROJECT_STATUS_DIR):
        return jsonify([])

    scan_locations = [
        (PROJECT_STATUS_DIR, False),
        (os.path.join(PROJECT_STATUS_DIR, 'archiv'), True),
    ]

    for scan_dir, is_deep_archived in scan_locations:
        if not os.path.exists(scan_dir):
            continue
        for filename in os.listdir(scan_dir):
            if not filename.endswith('.json'):
                continue
            filepath = os.path.join(scan_dir, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if data.get('origin_timeline') != timeline_id:
                    continue
                mtime = os.path.getmtime(filepath)
                last_updated = datetime.fromtimestamp(mtime).strftime("%d.%m.%Y %H:%M")
                archived_projects.append({
                    "name": data.get("name", "Unbekannt"),
                    "project_code": data.get("project_code", ""),
                    "engine_serial": data.get("engine_serial", "N/A"),
                    "customer": data.get("customer", "N/A"),
                    "engine_type": data.get("engine_type", "N/A"),
                    "progress_percent": data.get("progress_percent", 0),
                    "last_updated": last_updated,
                    "timestamp": mtime,
                    "filename": filename,
                    "uuid": data.get("uuid", ""),
                    "deviation_count": (_dev_counts := _count_deviations(data))[0],
                    "open_deviation_count": _dev_counts[1],
                    "is_deep_archived": is_deep_archived,
                })
            except Exception as e:
                print(f"Fehler beim Lesen des Archivs {filename}: {e}")

    archived_projects.sort(key=lambda x: x['timestamp'], reverse=True)
    return jsonify(archived_projects)


@app.route('/api/archive/project/deep-archive', methods=['POST'])
@login_required
def api_deep_archive_project():
    if 'ADMIN' not in session.get('roles', []):
        return jsonify({"error": "Keine Berechtigung."}), 403
    filename = re.sub(r'[^a-zA-Z0-9_\-.]', '', request.json.get('filename', ''))
    if not filename.endswith('.json'):
        return jsonify({"error": "Ungültiger Dateiname."}), 400
    src = os.path.join(PROJECT_STATUS_DIR, filename)
    if not os.path.exists(src):
        return jsonify({"error": "Datei nicht gefunden."}), 404
    archiv_dir = os.path.join(PROJECT_STATUS_DIR, 'archiv')
    os.makedirs(archiv_dir, exist_ok=True)
    try:
        with open(src, 'r', encoding='utf-8') as f:
            project_uuid = json.load(f).get('uuid', '')
    except Exception:
        project_uuid = ''
    shutil.move(src, os.path.join(archiv_dir, filename))
    if project_uuid:
        att_src = os.path.join(DEVIATION_ATTACHMENTS_DIR, project_uuid)
        if os.path.exists(att_src):
            att_dst_dir = os.path.join(archiv_dir, 'attachments')
            os.makedirs(att_dst_dir, exist_ok=True)
            shutil.move(att_src, os.path.join(att_dst_dir, project_uuid))
    return jsonify({"success": True})


@app.route('/api/archive/project/restore', methods=['POST'])
@login_required
def api_restore_project():
    if 'ADMIN' not in session.get('roles', []):
        return jsonify({"error": "Keine Berechtigung."}), 403
    filename = re.sub(r'[^a-zA-Z0-9_\-.]', '', request.json.get('filename', ''))
    if not filename.endswith('.json'):
        return jsonify({"error": "Ungültiger Dateiname."}), 400
    src = os.path.join(PROJECT_STATUS_DIR, 'archiv', filename)
    if not os.path.exists(src):
        return jsonify({"error": "Datei nicht gefunden."}), 404
    try:
        with open(src, 'r', encoding='utf-8') as f:
            project_uuid = json.load(f).get('uuid', '')
    except Exception:
        project_uuid = ''
    shutil.move(src, os.path.join(PROJECT_STATUS_DIR, filename))
    if project_uuid:
        att_src = os.path.join(PROJECT_STATUS_DIR, 'archiv', 'attachments', project_uuid)
        if os.path.exists(att_src):
            os.makedirs(DEVIATION_ATTACHMENTS_DIR, exist_ok=True)
            shutil.move(att_src, os.path.join(DEVIATION_ATTACHMENTS_DIR, project_uuid))
    return jsonify({"success": True})

@app.route('/api/deviation/attachments/<string:project_uuid>', methods=['POST'])
@login_required
def api_deviation_attachment_upload(project_uuid):
    if 'file' not in request.files:
        return jsonify({"error": "Keine Datei."}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({"error": "Leerer Dateiname."}), 400
    safe_uuid = re.sub(r'[^a-zA-Z0-9_\-]', '', project_uuid)
    try:
        upload_dir = os.path.join(DEVIATION_ATTACHMENTS_DIR, safe_uuid)
        os.makedirs(upload_dir, exist_ok=True)
        unique_filename = f"{int(datetime.now().timestamp())}_{secure_filename(file.filename)}"
        file.save(os.path.join(upload_dir, unique_filename))
        return jsonify({"filename": unique_filename}), 200
    except Exception as e:
        print(f"Deviation-Upload Fehler: {e}")
        return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/deviation/attachments/<string:project_uuid>/<path:filename>')
@login_required
def api_serve_deviation_attachment(project_uuid, filename):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_\-]', '', project_uuid)
    active_dir = os.path.join(DEVIATION_ATTACHMENTS_DIR, safe_uuid)
    if os.path.exists(os.path.join(active_dir, filename)):
        return send_from_directory(active_dir, filename)
    archiv_dir = os.path.join(PROJECT_STATUS_DIR, 'archiv', 'attachments', safe_uuid)
    return send_from_directory(archiv_dir, filename)


@app.route('/api/deviation/attachments/<string:project_uuid>/<path:filename>', methods=['DELETE'])
@login_required
def api_delete_deviation_attachment(project_uuid, filename):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_\-]', '', project_uuid)
    safe_name = secure_filename(filename)
    active_path = os.path.join(DEVIATION_ATTACHMENTS_DIR, safe_uuid, safe_name)
    archiv_path = os.path.join(PROJECT_STATUS_DIR, 'archiv', 'attachments', safe_uuid, safe_name)
    target = active_path if os.path.exists(active_path) else archiv_path
    if not os.path.exists(target):
        return jsonify({"error": "Datei nicht gefunden."}), 404
    try:
        os.remove(target)
        return jsonify({"ok": True}), 200
    except Exception as e:
        print(f"Deviation-Attachment Löschen Fehler: {e}")
        return jsonify({"error": "Löschen fehlgeschlagen."}), 500


@app.route('/api/change_password', methods=['POST'])
@login_required
def api_change_password():
    data = request.json
    user_id = session.get('user_id')

    current_pw = data.get('currentPassword')
    new_pw = data.get('newPassword')
    confirm_pw = data.get('confirmPassword')

    if not all([current_pw, new_pw, confirm_pw]):
        return jsonify({"error": "Bitte alle Felder ausfüllen."}), 400

    if new_pw != confirm_pw:
        return jsonify({"error": "Die neuen Passwörter stimmen nicht überein."}), 400

    # User in der globalen Liste finden
    user = next((u for u in ALL_USERS if u['id'] == user_id), None)

    if not user:
        return jsonify({"error": "Benutzer nicht gefunden."}), 404

    # Altes Passwort prüfen
    if user['password'] != current_pw:
        return jsonify({"error": "Das aktuelle Passwort ist falsch."}), 403

    # Neues Passwort setzen
    user['password'] = new_pw

    try:
        _save_users_csv()
        return jsonify({"message": "Passwort erfolgreich geändert."}), 200
    except Exception as e:
        print(f"Fehler beim Speichern des Passworts: {e}")
        return jsonify({"error": "Interner Speicherfehler."}), 500

@app.route('/api/training/upload', methods=['POST'])
@login_required
def api_training_upload():
    if 'file' not in request.files: return jsonify({"error": "Keine Datei"}), 400
    file = request.files['file']
    if file.filename == '': return jsonify({"error": "Leer"}), 400

    try:
        filename = secure_filename(f"{int(datetime.now().timestamp())}_{file.filename}")
        file.save(os.path.join(TRAINING_UPLOADS_DIR, filename))
        return jsonify({"path": f"/training/files/{filename}"}), 200
    except Exception as e:
        print(e)
        return jsonify({"error": "Fehler"}), 500


@app.route('/training/files/<path:filename>')
@login_required
def serve_training_file(filename):
    return send_from_directory(TRAINING_UPLOADS_DIR, filename)

@app.route('/api/training/data', methods=['GET', 'POST'])
@login_required
def api_training_data():
    if request.method == 'POST':
        data = request.json
        if save_json_file(TRAINING_DATA_FILE, data):
            return jsonify({"status": "success"}), 200
        return jsonify({"error": "Fehler beim Speichern"}), 500

    if os.path.exists(TRAINING_DATA_FILE):
        try:
            with open(TRAINING_DATA_FILE, 'r', encoding='utf-8') as f:
                return jsonify(json.load(f))
        except:
            pass
    # Default Struktur
    return jsonify({"catalog": [], "sessions": []})


@app.route('/api/training/relations', methods=['GET', 'POST'])
@login_required
def api_user_relations():
    if request.method == 'POST':
        data = request.json
        if save_json_file(USER_RELATIONS_FILE, data):
            return jsonify({"status": "success"}), 200
        return jsonify({"error": "Fehler beim Speichern"}), 500

    if os.path.exists(USER_RELATIONS_FILE):
        try:
            with open(USER_RELATIONS_FILE, 'r', encoding='utf-8') as f:
                return jsonify(json.load(f))
        except:
            pass
    return jsonify({})  # { "user_id": { "buddy": "uid", "groups": ["grp1"] } }


@app.route('/training_center')
@login_required
def serve_training_page():
    # ... Standard Boilerplate für User & Navigation ...
    user_name = get_current_username_from_session()
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname: user_initials = (vorname[0] + nachname[0]).upper()

    # Wir brauchen die User-Liste für die Dropdowns
    if not ALL_USERS: load_all_users_on_startup()

    return render_template_string(
        TRAINING_PAGE_HTML,  # Das bauen wir gleich
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        active_page='training',
        users=ALL_USERS,
        url_for=url_for,
        session=session
    )

@app.route('/api/projektplan')
@login_required
def api_projektplan():
    """Returns all timelines with their projects, milestones, manual events, and optional progress %."""
    try:
        config = load_app_config()
        today = date.today()
        today_str = today.isoformat()
        week_start = today - timedelta(days=today.weekday())   # Monday
        week_end   = week_start + timedelta(days=6)            # Sunday
        next_week_end = week_end + timedelta(days=7)           # Sunday of next week
        week_start_str    = week_start.isoformat()
        week_end_str      = week_end.isoformat()
        next_week_end_str = next_week_end.isoformat()

        def fmt_milestone(raw):
            if not raw: return ''
            d = parse_date_from_string(raw)
            return d.strftime('%d.%m') if d else raw

        def milestone_obj(raw, key):
            if not raw: return None
            d = parse_date_from_string(raw)
            if not d: return None
            return {'key': key, 'label': key, 'value': d.strftime('%d.%m'), 'iso': d.isoformat()}

        def project_dates(p):
            """All relevant dates (milestones + event starts/ends) for a project."""
            dates = []
            for key in ['BE', 'TS', 'TE', 'SA']:
                d = parse_date_from_string(p.get(key, ''))
                if d:
                    dates.append(d)
            for evt in p.get('events', []):
                for field in ['start', 'end']:
                    d = parse_date_from_string(evt.get(field, ''))
                    if d:
                        dates.append(d)
            return dates

        def show_active(p):
            """Active project visible if anything falls within this week or next week
            (including events that span across the window boundary)."""
            for evt in p.get('events', []):
                s = parse_date_from_string(evt.get('start', ''))
                e = parse_date_from_string(evt.get('end', ''))
                if s and e and s <= next_week_end and e >= week_start:
                    return True
            for d in project_dates(p):
                if week_start <= d <= next_week_end:
                    return True
            return False

        result = []
        for tl in config.get('timelines', []):
            data_file = tl.get('data_file')
            data_files = [data_file] if data_file else tl.get('data_files', [])
            data_files = [f for f in data_files if f]
            projects = load_timeline_projects_for_flask(data_files) if data_files else []

            # Only active projects with something in this week or next week
            visible = [p for p in projects if p.get('active', True) and show_active(p)]
            visible.sort(key=lambda p: parse_date_from_string(p.get('TS') or p.get('BE') or '') or date.max)

            proj_list = []
            for p in visible:
                # Try to load progress % from project status file
                progress = None
                raw_id = f"{p.get('project_code', '')}_{p.get('engine_serial', '')}"
                safe_id = re.sub(r'[^a-zA-Z0-9_-]', '', raw_id)
                status_path = os.path.join(PROJECT_STATUS_DIR, f'project_status_{safe_id}.json')
                if os.path.exists(status_path):
                    try:
                        with open(status_path, 'r', encoding='utf-8') as f:
                            progress = json.load(f).get('progress_percent')
                    except Exception:
                        pass

                # Only show events that overlap with the two-week window
                events = []
                for evt in p.get('events', []):
                    if evt.get('end', '') >= week_start_str and evt.get('start', '') <= next_week_end_str:
                        # Compute realEnd: exclusive-end convention means AM end_shift → real last day is end - 1
                        end_dt = parse_date_from_string(evt.get('end', ''))
                        end_shift = evt.get('end_shift', 'PM')
                        if end_dt and end_shift == 'AM':
                            real_end_str = (end_dt - timedelta(days=1)).isoformat()
                        else:
                            real_end_str = evt.get('end', '')
                        events.append({
                            'title': evt.get('title', ''),
                            'start': evt.get('start', ''),
                            'start_shift': evt.get('start_shift', 'AM'),
                            'end': evt.get('end', ''),
                            'end_shift': end_shift,
                            'real_end': real_end_str,
                            'color': evt.get('color', 'DEFAULT'),
                            'is_current': evt.get('start', '') <= today_str <= real_end_str,
                        })
                events.sort(key=lambda e: e.get('start', ''))

                milestones = [m for m in [
                    milestone_obj(p.get('BE', ''), 'BE'),
                    milestone_obj(p.get('TS', ''), 'TS'),
                    milestone_obj(p.get('TE', ''), 'TE'),
                    milestone_obj(p.get('SA', ''), 'SA'),
                ] if m]

                project_id = f"{p.get('project_code', '')}_{p.get('engine_serial', '')}"
                proj_comments = sorted(
                    [c for c in ALL_COMMENTS if c.get('project_id') == project_id],
                    key=lambda c: c.get('timestamp', ''), reverse=True
                )[:2]
                comments_out = [{'author': c.get('author', ''), 'timestamp': c.get('timestamp', '')[:10], 'text': c.get('comment', '')} for c in proj_comments]

                proj_list.append({
                    'engine_type': p.get('engine_type', ''),
                    'project_code': p.get('project_code', ''),
                    'engine_serial': p.get('engine_serial', ''),
                    'customer': p.get('customer', ''),
                    'milestones': milestones,
                    'progress': progress,
                    'events': events,
                    'comments': comments_out,
                })

            result.append({'id': tl['id'], 'name': tl['name'], 'projects': proj_list})

        return jsonify(result)
    except Exception as e:
        traceback.print_exc()
        return jsonify({"error": str(e)}), 500


@app.route('/api/analytics/skill_matrix')
@login_required
def api_get_skill_matrix():
    global_scheduler.load()
    history = global_scheduler.load_history()
    live = global_scheduler.assignments
    all_data = {**history, **live}

    matrix = {}
    all_engines = set()
    today_iso = date.today().isoformat()

    for date_iso, shifts in all_data.items():
        # Erfahrung zählt nur bis heute. Morgen geplante Jobs sind noch keine Erfahrung.
        if date_iso > today_iso:
            continue

        for shift, users in shifts.items():
            for uid, details in users.items():
                raw_engine = details.get('engine')
                task_name = details.get('task', 'Unbekannt')

                if not raw_engine or raw_engine == 'N/A': continue

                engine = normalize_engine_family(raw_engine)
                all_engines.add(engine)

                if uid not in matrix: matrix[uid] = {}
                if engine not in matrix[uid]:
                    matrix[uid][engine] = {"last_total": "1900-01-01", "tasks": {}}

                # Update Gesamt-Datum
                if date_iso > matrix[uid][engine]["last_total"]:
                    matrix[uid][engine]["last_total"] = date_iso

                # Update Task-Datum
                curr_task_date = matrix[uid][engine]["tasks"].get(task_name, "1900-01-01")
                if date_iso > curr_task_date:
                    matrix[uid][engine]["tasks"][task_name] = date_iso

    if not ALL_USERS: load_all_users_on_startup()
    user_map = {u['id']: f"{u['nachname']}, {u['vorname']}" for u in ALL_USERS}

    result = []
    for uid, engines_data in matrix.items():
        name = user_map.get(uid, uid)
        result.append({
            "id": uid,
            "name": name,
            "skills": engines_data  # Das ganze Objekt
        })

    return jsonify({
        "matrix": result,
        "engines": sorted(list(all_engines))
    })

@app.route('/api/maintenance/logs', methods=['GET', 'POST'])
@login_required
def api_maintenance_logs():
    if request.method == 'POST':
        data = request.json
        if not data or 'logs' not in data:
            return jsonify({"error": "Keine Logs empfangen"}), 400

        if save_json_file(MAINTENANCE_LOGBOOK_FILE, data['logs']):
            return jsonify({"status": "success"}), 200
        return jsonify({"error": "Speichern fehlgeschlagen"}), 500

    # GET
    if os.path.exists(MAINTENANCE_LOGBOOK_FILE):
        try:
            with open(MAINTENANCE_LOGBOOK_FILE, 'r', encoding='utf-8') as f:
                return jsonify(json.load(f))
        except:
            return jsonify([])
    return jsonify([])

@app.route('/api/test_equipment/rename_storage', methods=['POST'])
@login_required
def api_rename_storage():
    data = request.json
    storage_type = data.get('type')  # 'trolleys' oder 'storage_units'
    old_name = data.get('oldName')
    new_name = data.get('newName')

    if not all([storage_type, old_name, new_name]):
        return jsonify({"error": "Fehlende Parameter"}), 400

    # Bug 3 Fix: gesamter Read-Modify-Write für die Hardware-Datei unter einem Lock —
    # verhindert TOCTOU bei gleichzeitigen Umbenennungen.
    count_hw = 0
    with _get_file_lock(TEST_EQUIPMENT_DATA_FILE):
        te_data = _load_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, default_val=None)
        if not isinstance(te_data, dict):
            return jsonify({"error": "DB Fehler"}), 500

        container_dict = te_data.get(storage_type, {})

        if new_name in container_dict:
            return jsonify({"error": f"Name '{new_name}' existiert bereits."}), 409
        if old_name not in container_dict:
            return jsonify({"error": "Ursprungsobjekt nicht gefunden."}), 404

        # 1. Key umbenennen (Daten kopieren, alten löschen)
        container_dict[new_name] = container_dict.pop(old_name)

        # 2. Hardware Locations updaten
        inventory = te_data.get('inventory', {})
        for pn, part in inventory.items():
            for s in part.get('serials', []):
                if s.get('location') == old_name:
                    s['location'] = new_name
                    count_hw += 1

        if not _write_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, te_data):
            return jsonify({"error": "Speichern fehlgeschlagen"}), 500

    # 3. Facility Structure außerhalb des Locks updaten — save_json_file holt eigenen Lock
    facility_count = 0
    try:
        if os.path.exists(FACILITY_STRUCTURE_FILE):
            with open(FACILITY_STRUCTURE_FILE, 'r', encoding='utf-8') as f:
                structure = json.load(f)

            def update_node_storage(nodes):
                c = 0
                for node in nodes:
                    if node.get('storageLink') == old_name:
                        node['storageLink'] = new_name
                        c += 1
                        print(f"INFO: Facility Node '{node.get('name')}' Storage Link aktualisiert.")
                    if 'children' in node:
                        c += update_node_storage(node['children'])
                return c

            facility_count += update_node_storage(structure.get('betriebseinheiten', []))
            facility_count += update_node_storage(structure.get('gebaeude', []))

            if facility_count > 0:
                save_json_file(FACILITY_STRUCTURE_FILE, structure)
    except Exception as e:
        print(f"Fehler beim Facility Sync: {e}")

    # Bug 6 Fix: Hash nach dem Schreiben aus der Datei lesen statt vom In-Memory-Objekt berechnen
    new_hash = get_current_hardware_file_hash()
    return jsonify({
        "success": True,
        "message": f"Umbenannt. {count_hw} Hardware-Teile und {facility_count} Facility-Links aktualisiert.",
        "version_hash": new_hash
    }), 200


@app.route('/api/test_equipment/container/<container_type>/<path:container_name>', methods=['PUT', 'DELETE'])
@login_required
def api_upsert_container(container_type, container_name):
    """
    Granularer Read-Modify-Write für einen einzelnen Rüstwagen oder Lagerort.
    PUT  — Container anlegen oder überschreiben (+ Inventory-Locations synchronisieren).
    DELETE — Container löschen (+ Inventory-Locations leeren).
    Verhindert Last-Write-Wins durch atomares Lock.
    """
    if container_type not in ('trolleys', 'storage_units'):
        return jsonify({"error": "Ungültiger container_type"}), 400

    with _get_file_lock(TEST_EQUIPMENT_DATA_FILE):
        te_data = _load_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, default_val=None)
        if not isinstance(te_data, dict):
            return jsonify({"error": "DB Fehler"}), 500

        containers = te_data.setdefault(container_type, {})
        inventory = te_data.get('inventory', {})

        if request.method == 'DELETE':
            if container_name not in containers:
                return jsonify({"error": "Container nicht gefunden"}), 404
            del containers[container_name]
            # Inventory-Locations leeren
            for part in inventory.values():
                for s in part.get('serials', []):
                    if s.get('location') == container_name:
                        s['location'] = ''
            recalculate_kit_status(te_data)
            _write_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, te_data)
            new_hash = compute_hardware_data_hash(te_data)
            return jsonify({"status": "success", "version_hash": new_hash}), 200

        # PUT
        payload = request.json
        if payload is None:
            return jsonify({"error": "Kein JSON-Body"}), 400

        container_data = payload.get('container_data', {})
        items = container_data.get('items', [])

        # Inventory-Locations: erst alle alten Einträge für diesen Container löschen
        for part in inventory.values():
            for s in part.get('serials', []):
                if s.get('location') == container_name:
                    s['location'] = ''

        # Dann neue Items setzen
        for item in items:
            pn = item.get('pn')
            sn = item.get('sn')
            if pn and sn and pn in inventory:
                serial_entry = next((s for s in inventory[pn].get('serials', []) if s.get('sn') == sn), None)
                if serial_entry:
                    serial_entry['location'] = container_name

        # Container-Daten schreiben (completeness_log nicht überschreiben)
        existing = containers.get(container_name, {})
        container_data.pop('completeness_log', None)
        existing_log = existing.get('completeness_log')
        containers[container_name] = container_data
        if existing_log is not None:
            containers[container_name]['completeness_log'] = existing_log

        recalculate_kit_status(te_data)
        _write_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, te_data)

    # Sync außerhalb des Locks (liest/schreibt andere Dateien)
    sync_trolleys_and_storage(te_data)
    # Bug 6 Fix: Hash erst nach sync_trolleys_and_storage aus der Datei lesen —
    # der Sync kann die Datei nochmals schreiben und den Hash ändern.
    new_hash = get_current_hardware_file_hash()
    return jsonify({"status": "success", "version_hash": new_hash}), 200


@app.route('/api/completeness_log', methods=['GET'])
@login_required
def api_get_completeness_log():
    """Gibt Vollständigkeitsprotokolle zurück, optional gefiltert nach Container."""
    container = request.args.get('container')
    log_data = load_completeness_log()
    entries = log_data.get('entries', [])
    if container:
        entries = [e for e in entries if e.get('container') == container]
    entries.sort(key=lambda e: e.get('timestamp', ''), reverse=True)
    return jsonify(entries)


@app.route('/api/completeness_log', methods=['POST'])
@login_required
def api_save_completeness_entry():
    """Speichert einen neuen oder aktualisierten Prüfeintrag."""
    entry = request.json
    if not entry or not entry.get('container') or not entry.get('date'):
        return jsonify({"error": "Pflichtfelder fehlen (container, date)"}), 400

    log_data = load_completeness_log()
    entries = log_data.get('entries', [])

    existing_idx = next((i for i, e in enumerate(entries) if e.get('id') == entry.get('id')), -1)
    if existing_idx >= 0:
        entries[existing_idx] = entry
    else:
        if not entry.get('id'):
            entry['id'] = 'chk_' + uuid.uuid4().hex[:9]
        if not entry.get('timestamp'):
            entry['timestamp'] = datetime.utcnow().isoformat() + 'Z'
        entries.append(entry)

    log_data['entries'] = entries
    if save_completeness_log(log_data):
        _update_last_checked_snapshot(entry)
        return jsonify({"status": "success", "entry": entry}), 200
    return jsonify({"error": "Speicherfehler"}), 500


@app.route('/api/completeness_log/<string:entry_id>', methods=['DELETE'])
@login_required
def api_delete_completeness_entry(entry_id):
    """Löscht einen Prüfeintrag anhand seiner ID."""
    log_data = load_completeness_log()
    before = len(log_data.get('entries', []))
    log_data['entries'] = [e for e in log_data.get('entries', []) if e.get('id') != entry_id]
    if len(log_data['entries']) == before:
        return jsonify({"error": "Eintrag nicht gefunden"}), 404
    if save_completeness_log(log_data):
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speicherfehler"}), 500


@app.route('/api/completeness_config', methods=['GET'])
@login_required
def api_get_completeness_config():
    return jsonify(load_completeness_config())


@app.route('/api/completeness_config', methods=['POST'])
@login_required
def api_save_completeness_config():
    data = request.json
    if not data:
        return jsonify({"error": "Keine Daten"}), 400
    if save_completeness_config(data):
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speicherfehler"}), 500


@app.route('/api/hardware/upload', methods=['POST'])
@login_required
def api_hardware_upload():
    if 'file' not in request.files:
        return jsonify({"error": "Keine Datei."}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({"error": "Leerer Dateiname."}), 400

    try:
        filename = secure_filename(file.filename)
        # Timestamp für Eindeutigkeit
        unique_filename = f"{int(datetime.now().timestamp())}_{filename}"

        # Speichern
        file.save(os.path.join(HARDWARE_UPLOADS_DIR, unique_filename))

        # Pfad für Frontend zurückgeben
        return jsonify({
            "filename": filename,  # Originalname für Anzeige
            "stored_name": unique_filename,
            "path": f"/hardware/files/{unique_filename}"
        }), 200
    except Exception as e:
        print(f"Hardware Upload Fehler: {e}")
        return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/hardware/files/<path:filename>')
@login_required
def serve_hardware_file(filename):
    return send_from_directory(HARDWARE_UPLOADS_DIR, filename)

@app.route('/facility/model/current')
@login_required
def serve_current_facility_model():
    """Liefert das aktuelle 3D-Modell aus dem geschützten db/facility Ordner."""
    if os.path.exists(FACILITY_MODEL_PATH):
        return send_from_directory(FACILITY_MODELS_DIR, FACILITY_MODEL_FILENAME)
    else:
        return "Modell nicht gefunden", 404

@app.route('/api/facility/storage_options', methods=['GET'])
@login_required
def api_get_facility_storage_options():
    """Liefert eine Liste aller Rüstwagen und Lagerorte für das Dropdown."""
    try:
        te_data = load_test_equipment_data()
        if te_data is None: return jsonify([])

        options = []
        # Rüstwagen
        for name in te_data.get('trolleys', {}):
            options.append({"id": name, "name": name, "type": "Trolley"})
        # Lagerorte
        for name in te_data.get('storage_units', {}):
            options.append({"id": name, "name": name, "type": "Storage"})

        options.sort(key=lambda x: x['name'])
        return jsonify(options)
    except Exception as e:
        print(f"Fehler beim Laden der Storage Options: {e}")
        return jsonify([])


@app.route('/api/facility/storage_content', methods=['GET'])
@login_required
def api_get_facility_storage_content():
    """Liefert den Inhalt eines Lagerorts."""
    storage_name = request.args.get('name')
    if not storage_name: return jsonify([])

    try:
        te_data = load_test_equipment_data()
        if te_data is None: return jsonify([])

        # Suche in Trolleys und Storage Units
        container = te_data.get('trolleys', {}).get(storage_name) or \
                    te_data.get('storage_units', {}).get(storage_name)

        if not container: return jsonify([])

        # Inventar laden (nur als Fallback nötig)
        inventory = te_data.get('inventory', {})

        items = []
        for item in container.get('items', []):
            # 1. Versuch: Name direkt aus dem Item (Freitext oder gesyncter Name)
            desc = item.get('name')

            # 2. Versuch: Fallback über Inventar-Lookup (falls alte Datenstruktur ohne Name)
            if not desc:
                part = inventory.get(item.get('pn'), {})
                desc = part.get('name', 'Unbekannt')

            items.append({
                "pn": item.get('pn', '-'),
                "sn": item.get('sn', '-'),
                "qty": item.get('qty', 1),
                "box": item.get('box', ''),
                "level": item.get('level', ''),
                "desc": desc,
                "checkedOut": bool(item.get('checkedOutBy')),
                "checkedOutBy": item.get('checkedOutBy', ''),
                "checkedOutReason": item.get('checkedOutReason', '')
            })

        # Sortieren nach Ebene -> Box
        items.sort(key=lambda x: (str(x['level']), str(x['box'])))

        return jsonify(items)
    except Exception as e:
        print(f"Fehler beim Laden des Storage Contents: {e}")
        return jsonify([])

@app.route('/api/facility/storage_index', methods=['GET'])
@login_required
def api_facility_storage_index():
    """Kompakter Suchindex aller Lager- und Trolley-Inhalte für die Volltextsuche."""
    try:
        te_data = load_test_equipment_data()
        if te_data is None: return jsonify({})
        inventory = te_data.get('inventory', {})
        result = {}
        for collection_key in ('trolleys', 'storage_units'):
            for name, container in te_data.get(collection_key, {}).items():
                items = []
                for item in container.get('items', []):
                    desc = item.get('name') or inventory.get(item.get('pn', ''), {}).get('name', '')
                    items.append({
                        'pn': item.get('pn', ''),
                        'sn': item.get('sn', ''),
                        'desc': desc or ''
                    })
                if items:
                    result[name] = items
        return jsonify(result)
    except Exception as e:
        print(f"Fehler beim Erstellen des Storage-Index: {e}")
        return jsonify({})

@app.route('/api/admin/save_sfm_text', methods=['POST'])
@admin_required
def api_save_sfm_text():
    data = request.json
    if not data or 'key' not in data:
        return jsonify({"error": "Ungültige Daten"}), 400

    key = data['key']  # z.B. "motd"

    current_texts = load_sfm_texts()

    # Update des spezifischen Text-Blocks
    current_texts[key] = {
        "text": data.get('text', ''),
        "type": data.get('type', 'info'),
        "visible": data.get('visible', False)
    }

    if save_sfm_texts_data(current_texts):
        return jsonify({"message": "Text gespeichert."}), 200
    else:
        return jsonify({"error": "Fehler beim Speichern."}), 500

@app.route('/api/facility/units', methods=['GET'])
@login_required
def api_get_facility_units():
    """Gibt eine flache Liste aller Betriebseinheiten für Dropdowns zurück."""
    try:
        data = {}
        if os.path.exists(FACILITY_STRUCTURE_FILE):
            with open(FACILITY_STRUCTURE_FILE, 'r', encoding='utf-8') as f:
                data = json.load(f)

        # Wir nehmen nur die 'betriebseinheiten', Gebäude sind meist nur Hüllen
        units = data.get('betriebseinheiten', [])
        flat_list = get_flat_facility_units(units)

        return jsonify(flat_list)
    except Exception as e:
        print(f"Fehler beim Laden der Facility Units: {e}")
        return jsonify([])


@app.route('/facility/viewer')
@login_required
def serve_facility_viewer():
    """Zeigt den 3D-Viewer mit Strukturbaum an."""
    structure_data = {}

    # Sicherstellen, dass Datei existiert (doppelte Sicherheit)
    init_facility_data()

    try:
        if os.path.exists(FACILITY_STRUCTURE_FILE):
            with open(FACILITY_STRUCTURE_FILE, 'r', encoding='utf-8') as f:
                structure_data = json.load(f)
        else:
            structure_data = FACILITY_EMPTY_DATA
    except Exception as e:
        print(f"Fehler beim Laden der Struktur: {e}")
        structure_data = FACILITY_EMPTY_DATA

    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()
    elif session.get('username_display'):
        # Fallback auf ersten Buchstaben des Display-Namens, falls Vorname/Nachname fehlen
        user_initials = session.get('username_display')[0].upper()

    wiki_data = {}
    try:
        if os.path.exists(FACILITY_WIKI_FILE):
            with open(FACILITY_WIKI_FILE, 'r', encoding='utf-8') as f:
                wiki_data = json.load(f)
    except Exception:
        pass

    return render_template_string(
        FACILITY_VIEWER_HTML,
        structure_data=structure_data,
        wiki_data=wiki_data,
        url_for=url_for,
        session=session,
        current_user_initials=user_initials
    )


@app.route('/facility/editor')
@login_required
# @admin_required  <-- Optional: Aktivieren, wenn nur Admins editieren sollen
def serve_facility_editor():
    """Zeigt den 3D-Editor zum Bearbeiten der Geometrie an."""
    return render_template_string(TEMPLATE_EDITOR, url_for=url_for)


@app.route('/api/facility/save_structure', methods=['POST'])
@login_required
def api_facility_save_structure():
    """Speichert die logische Struktur (JSON)."""
    data = request.get_json()
    if data is None:
        return jsonify({"status": "error", "message": "No data received"}), 400

    # Atomares Speichern nutzen (unsere Helper-Funktion)
    if save_json_file(FACILITY_STRUCTURE_FILE, data):
        sync_facility_names_to_maintenance(data)
        return jsonify({"status": "success"}), 200
    else:
        return jsonify({"status": "error", "message": "Save failed"}), 500


@app.route('/api/facility/wiki/<node_id>', methods=['GET', 'POST'])
@login_required
def api_facility_wiki(node_id):
    """Liest oder speichert Wiki-Daten (Notizen, Links) für einen Strukturknoten."""
    if request.method == 'GET':
        try:
            wiki = {}
            if os.path.exists(FACILITY_WIKI_FILE):
                with open(FACILITY_WIKI_FILE, 'r', encoding='utf-8') as f:
                    wiki = json.load(f)
            return jsonify(wiki.get(node_id, {}))
        except Exception as e:
            return jsonify({}), 500
    else:
        try:
            data = request.get_json()
            now = datetime.now().isoformat()
            vorname = session.get('user_vorname', '')
            nachname = session.get('user_nachname', '')
            user_display = f"{vorname} {nachname}".strip() or session.get('username_display', 'Unbekannt')
            with _get_file_lock(FACILITY_WIKI_FILE):
                wiki = {}
                if os.path.exists(FACILITY_WIKI_FILE):
                    with open(FACILITY_WIKI_FILE, 'r', encoding='utf-8') as f:
                        wiki = json.load(f)
                existing = wiki.get(node_id, {})
                # Vorherige Version in Verlauf schreiben (nur wenn bereits Inhalt vorhanden)
                if existing.get('geaendert_am'):
                    history = {}
                    if os.path.exists(FACILITY_WIKI_HISTORY_FILE):
                        with open(FACILITY_WIKI_HISTORY_FILE, 'r', encoding='utf-8') as f:
                            history = json.load(f)
                    versions = history.get(node_id, [])
                    versions.insert(0, existing)
                    history[node_id] = versions[:FACILITY_WIKI_HISTORY_MAX]
                    with open(FACILITY_WIKI_HISTORY_FILE, 'w', encoding='utf-8') as f:
                        json.dump(history, f, ensure_ascii=False, indent=2)
                data['erstellt_von'] = existing.get('erstellt_von', user_display)
                data['erstellt_am']  = existing.get('erstellt_am',  now)
                data['geaendert_von'] = user_display
                data['geaendert_am']  = now
                wiki[node_id] = data
                with open(FACILITY_WIKI_FILE, 'w', encoding='utf-8') as f:
                    json.dump(wiki, f, ensure_ascii=False, indent=2)
            return jsonify({'success': True, 'meta': {'erstellt_von': data['erstellt_von'], 'erstellt_am': data['erstellt_am'], 'geaendert_von': user_display, 'geaendert_am': now}})
        except Exception as e:
            return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/api/facility/wiki/<node_id>/history')
@login_required
def api_facility_wiki_history(node_id):
    """Gibt den Änderungsverlauf eines Wiki-Knotens zurück."""
    try:
        if os.path.exists(FACILITY_WIKI_HISTORY_FILE):
            with open(FACILITY_WIKI_HISTORY_FILE, 'r', encoding='utf-8') as f:
                history = json.load(f)
            return jsonify(history.get(node_id, []))
        return jsonify([])
    except Exception:
        return jsonify([]), 500


@app.route('/api/facility/wiki/<node_id>/orders')
@login_required
def api_facility_wiki_orders(node_id):
    """Gibt aktive Wartungsaufträge für einen Facility-Node zurück."""
    try:
        data = load_maintenance_data()
        orders = data.get('orders', [])
        structure = {}
        if os.path.exists(FACILITY_STRUCTURE_FILE):
            with open(FACILITY_STRUCTURE_FILE, 'r', encoding='utf-8') as f:
                structure = json.load(f)

        def find_node(nodes, nid, prefix=''):
            for i, n in enumerate(nodes):
                num = f"{prefix}{i+1}"
                if n.get('id') == nid:
                    return n, num
                if n.get('children'):
                    found, found_num = find_node(n['children'], nid, f"{num}.")
                    if found:
                        return found, found_num
            return None, None

        node, node_num = find_node(structure.get('betriebseinheiten', []), node_id)
        if not node:
            node, node_num = find_node(structure.get('gebaeude', []), node_id)

        node_names = set()
        if node:
            node_names.add(node.get('name', ''))
            if node_num:
                node_names.add(f"{node_num} {node.get('name', '')}")

        orders_map = {o.get('id'): o for o in orders}
        result = []
        for order in orders:
            if order.get('status') == 'Completed':
                continue
            fid = order.get('facilityUnitId')
            pi = order.get('plantIndex', '')
            matches = (fid == node_id) or (pi in node_names and pi != '')
            if not matches and order.get('sourceTask'):
                parent = orders_map.get(order['sourceTask'])
                if parent:
                    pfid = parent.get('facilityUnitId')
                    ppi = parent.get('plantIndex', '')
                    matches = (pfid == node_id) or (ppi in node_names and ppi != '')
            if not matches:
                continue
            is_unit_blocker = order.get('isUnitBlocker')
            if is_unit_blocker is None:
                is_unit_blocker = order.get('isBlocker') and not bool(order.get('blockedAssetIds'))
            level = 0
            if is_unit_blocker:
                level = 3
            elif order.get('priority') in ('Critical', 'High'):
                level = 2
            elif order.get('type') == 'I' or order.get('status') == 'In Progress':
                level = 1
            elif order.get('type') == 'M' and order.get('status') == 'Pending':
                try:
                    if datetime.strptime(order['date'], '%Y-%m-%d').date() <= date.today() + timedelta(days=14):
                        level = 2
                except Exception:
                    pass
            result.append({
                'id': order.get('id', ''),
                'title': order.get('task', ''),
                'type': order.get('type', 'M'),
                'priority': order.get('priority', 'Medium'),
                'status': order.get('status', ''),
                'date': order.get('date', ''),
                'level': level
            })
        result.sort(key=lambda x: (-x['level'], x.get('date') or 'zzz'))
        return jsonify(result)
    except Exception as e:
        return jsonify([]), 500


@app.route('/api/facility/wiki/<node_id>/attachments', methods=['POST'])
@login_required
def api_facility_upload_attachment(node_id):
    if 'file' not in request.files:
        return jsonify({'success': False, 'error': 'No file'}), 400
    f = request.files['file']
    if not f.filename:
        return jsonify({'success': False, 'error': 'No filename'}), 400
    original_name = secure_filename(f.filename)
    unique_name = str(uuid.uuid4())[:8] + '_' + original_name
    node_dir = os.path.join(FACILITY_ATTACHMENTS_DIR, node_id)
    os.makedirs(node_dir, exist_ok=True)
    file_path = os.path.join(node_dir, unique_name)
    f.save(file_path)
    size = os.path.getsize(file_path)
    now = datetime.now().isoformat()
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    user_display = f"{vorname} {nachname}".strip() or session.get('username_display', 'Unbekannt')
    return jsonify({
        'success': True,
        'name': unique_name,
        'original_name': original_name,
        'size': size,
        'mime': f.mimetype or '',
        'uploaded_by': user_display,
        'uploaded_at': now
    })


@app.route('/api/facility/attachments/<node_id>/<filename>')
@login_required
def api_facility_serve_attachment(node_id, filename):
    node_dir = os.path.join(FACILITY_ATTACHMENTS_DIR, node_id)
    return send_from_directory(node_dir, filename)


@app.route('/api/facility/wiki/<node_id>/attachments/<filename>', methods=['DELETE'])
@login_required
def api_facility_delete_attachment(node_id, filename):
    file_path = os.path.join(FACILITY_ATTACHMENTS_DIR, node_id, filename)
    try:
        if os.path.exists(file_path):
            os.remove(file_path)
        return jsonify({'success': True})
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/api/facility/room_status')
@login_required
def api_facility_room_status():
    """
    Berechnet den Status der Räume/Objekte.
    Priorität: UUID-Match > Namens-Match.
    """
    status_map = {}

    try:
        if os.path.exists(FACILITY_STRUCTURE_FILE):
            with open(FACILITY_STRUCTURE_FILE, 'r', encoding='utf-8') as f:
                structure = json.load(f)
        else:
            structure = FACILITY_EMPTY_DATA

        data = load_maintenance_data()
        orders = data.get('orders', [])
    except Exception as e:
        print(f"Fehler beim Laden der Daten für Status: {e}")
        return jsonify({})

    # 1. Maps aufbauen:
    id_to_mesh_map = {}
    name_to_mesh_map = {}
    # Für Knoten ohne Meshes: nächster Vorfahre mit Meshes
    id_to_ancestor_map = {}
    name_to_ancestor_map = {}

    def map_structure(nodes, prefix="", ancestor_meshes=None):
        for i, node in enumerate(nodes):
            current_index_str = f"{prefix}{i + 1}"

            # Der volle Name ("1.1 Name")
            full_name = f"{current_index_str} {node.get('name', '')}"

            meshes = node.get('meshNames', [])

            if meshes:
                # Mapping via ID (Sicher!)
                if node.get('id'):
                    id_to_mesh_map[node['id']] = meshes

                # Mapping via Namen (Fallback)
                name_to_mesh_map[full_name] = meshes
                if node.get('name'):
                    name_to_mesh_map[node.get('name')] = meshes

                effective_ancestor = meshes
            else:
                # Kein eigenes Mesh: Vorfahren-Mesh registrieren
                if ancestor_meshes:
                    if node.get('id'):
                        id_to_ancestor_map[node['id']] = ancestor_meshes
                    name_to_ancestor_map[full_name] = ancestor_meshes
                    if node.get('name'):
                        name_to_ancestor_map[node.get('name')] = ancestor_meshes
                effective_ancestor = ancestor_meshes

            if 'children' in node:
                map_structure(node['children'], current_index_str + ".", effective_ancestor)

    map_structure(structure.get('betriebseinheiten', []))
    map_structure(structure.get('gebaeude', []))

    # 2. Aufträge analysieren
    mesh_status_level = {}
    mesh_bubbled_orders = {}  # mesh → Liste gebubblter Aufträge für Frontend-Badge
    orders_map = {o['id']: o for o in orders}

    for order in orders:
        if order.get('status') == 'Completed': continue

        # Bestimme die Meshes für diesen Auftrag
        target_meshes = []
        is_bubbled = False

        # A) Versuch über UUID (Neu & Robust)
        f_id = order.get('facilityUnitId')

        # Fallback: ID vom Parent holen, falls Issue
        if not f_id and order.get('sourceTask'):
            parent = orders_map.get(order.get('sourceTask'))
            if parent: f_id = parent.get('facilityUnitId')

        if f_id and f_id in id_to_mesh_map:
            target_meshes = id_to_mesh_map[f_id]

        # B) Versuch über Name (Legacy Fallback) — plant_index früh berechnen für späteres Bubbling
        plant_index = order.get('plantIndex')
        if not plant_index and order.get('sourceTask'):
            parent = orders_map.get(order.get('sourceTask'))
            if parent: plant_index = parent.get('plantIndex')

        if not target_meshes and plant_index and plant_index in name_to_mesh_map:
            target_meshes = name_to_mesh_map[plant_index]

        # C) Bubbling: Auftrag eines Knotens ohne Mesh → nächster Vorfahre mit Mesh
        if not target_meshes:
            if f_id and f_id in id_to_ancestor_map:
                target_meshes = id_to_ancestor_map[f_id]
                is_bubbled = True
            elif plant_index and plant_index in name_to_ancestor_map:
                target_meshes = name_to_ancestor_map[plant_index]
                is_bubbled = True

        if not target_meshes: continue

        # 1. Unit Blocker Status ermitteln
        is_unit_blocker = order.get('isUnitBlocker')
        if is_unit_blocker is None:
            # Legacy Fallback: Wenn das Feld fehlt, raten wir anhand der Assets
            is_blocker_legacy = order.get('isBlocker')
            has_blocked_assets = bool(order.get('blockedAssetIds'))
            # Wenn 'isBlocker' true ist, aber KEINE Assets blockiert sind -> War wohl als Unit-Blocker gemeint
            is_unit_blocker = is_blocker_legacy and not has_blocked_assets

        # 2. Level berechnen
        new_level = 0

        if is_unit_blocker:
            # HÖCHSTE STUFE: Nur explizite Anlagen-Sperre macht Rot
            new_level = 3  # Rot ("heiss" / Blocked)

        elif order.get('priority') == 'Critical':
            # Critical, aber kein Anlagen-Blocker -> Orange/Gelb (Wartung/Warnung)
            # Das signalisiert: "Hier ist was Dringendes, aber der Raum ist nicht tot."
            new_level = 2

        elif order.get('priority') == 'High':
            new_level = 2  # Orange

        elif order.get('type') == 'I' or order.get('status') == 'In Progress':
            # Normale Issues oder laufende Arbeiten -> Blau
            new_level = 1

            # Anstehende Wartung (Bald fällig)
        elif order.get('type') == 'M' and order.get('status') == 'Pending':
            try:
                if datetime.strptime(order.get('date'), '%Y-%m-%d').date() <= (date.today() + timedelta(days=14)):
                    if new_level < 2: new_level = 2
            except:
                pass

        # Auf alle zugehörigen Meshes anwenden
        for mesh in target_meshes:
            current = mesh_status_level.get(mesh, 0)
            if new_level > current:
                mesh_status_level[mesh] = new_level
            # Gebubblte Aufträge tracken (für Badge + Detail-Popup)
            if is_bubbled and new_level > 0:
                if mesh not in mesh_bubbled_orders:
                    mesh_bubbled_orders[mesh] = []
                mesh_bubbled_orders[mesh].append({
                    'id': order.get('id', ''),
                    'sourceName': plant_index or f_id or '',
                    'title': order.get('task', ''),
                    'type': order.get('type', 'M'),
                    'priority': order.get('priority', 'Medium'),
                    'status': order.get('status', ''),
                    'date': order.get('date', ''),
                    'level': new_level
                })

    # 3. Ergebnis bauen
    for mesh, level in mesh_status_level.items():
        if level == 3:
            status_map[mesh] = "heiss"
        elif level == 2:
            status_map[mesh] = "Wartung_geplant"
        elif level == 1:
            status_map[mesh] = "in_Betrieb"

    # Gebubblte Aufträge anhängen (nur wenn vorhanden)
    if mesh_bubbled_orders:
        status_map['_bubbled'] = mesh_bubbled_orders

    return jsonify(status_map)

@app.route('/api/facility/unit_orders', methods=['GET'])
@login_required
def api_facility_unit_orders():
    """
    Liefert gefilterte Wartungsaufträge für eine Unit.
    Filter:
    - Keine abgeschlossenen Aufträge. - Issues: Alle offenen. - Maintenance: Aktive, Überfällige und Zukunft (max 14 Tage). - Actions: Nur aktive (In Progress/Review), keine geplanten (Pending).
    """
    unit_name = request.args.get('unit')
    if not unit_name:
        return jsonify([])

    try:
        data = load_maintenance_data()
        orders = data.get('orders', [])
    except Exception as e:
        print(f"Fehler beim Laden der Orders: {e}")
        return jsonify([])

    relevant_orders = []

    # Datumsgrenzen berechnen
    today = date.today()
    future_limit = today + timedelta(days=14)

    # Lookup Map für Parent-Beziehungen
    orders_map = {o['id']: o for o in orders}

    for order in orders:
        # 1. Grundfilter: Nichts Abgeschlossenes
        if order.get('status') == 'Completed': continue

        # 2. Namens-Matching (Unit)
        plant_index = order.get('plantIndex', '')
        is_match = False

        if plant_index == unit_name:
            is_match = True
        elif not plant_index and order.get('sourceTask'):
            parent = orders_map.get(order.get('sourceTask'))
            if parent and parent.get('plantIndex') == unit_name:
                is_match = True

        if not is_match: continue

        # 3. Logische Filterung nach Typ und Zeit
        order_type = order.get('type')
        status = order.get('status')
        order_date_str = order.get('date')
        show_order = False

        if order_type == 'I':
            # Issues: Immer anzeigen, solange nicht Completed
            show_order = True

        elif order_type == 'A':
            # Actions: Nur anzeigen, wenn aktiv bearbeitet oder im Review
            if status in ['In Progress', 'Review Ready']:
                show_order = True
            else:
                # Pending Actions ausblenden
                show_order = False

        elif order_type == 'M' or not order_type:  # Maintenance (oder Legacy ohne Typ)
            if status in ['In Progress', 'Review Ready']:
                show_order = True
            elif status == 'Pending':
                # Datum prüfen
                try:
                    due_date = datetime.strptime(order_date_str, '%Y-%m-%d').date()
                    if due_date <= future_limit:
                        show_order = True  # Überfällig oder in den nächsten 14 Tagen
                    else:
                        show_order = False  # Zu weit in der Zukunft
                except:
                    # Falls Datum fehlt/falsch: Sicherheitshalber anzeigen
                    show_order = True

        if show_order:
            relevant_orders.append({
                "id": order.get('id'),
                "type": order_type,
                "title": order.get('task'),
                "priority": order.get('priority', 'Medium'),
                "status": status,
                "isBlocker": order.get('isBlocker', False),
                "date": order_date_str
            })

    # 4. Sortierung (Score-basiert für Dringlichkeit)
    def sort_key(o):
        score = 0
        # Höchste Prio: Blocker & Kritische Issues
        if o['type'] == 'I' and (o['priority'] == 'Critical' or o['isBlocker']):
            score += 100

        # Hohe Prio: Aktive Arbeit
        if o['status'] == 'In Progress':
            score += 80

        # Mittlere Prio: Überfällige Wartung
        if o['type'] == 'M' and o['status'] == 'Pending':
            try:
                if datetime.strptime(o['date'], '%Y-%m-%d').date() < today:
                    score += 60
            except:
                pass

        # Prio für High Issues
        if o['type'] == 'I' and o['priority'] == 'High':
            score += 50

        return score

    relevant_orders.sort(key=sort_key, reverse=True)

    return jsonify(relevant_orders)

@app.route('/api/facility/save_model', methods=['POST'])
@login_required
def api_facility_save_model():
    """Speichert die GLB-Datei (3D-Modell) und erstellt ein Backup."""
    if 'file' not in request.files:
        return jsonify({"status": "error", "message": "Keine Datei hochgeladen"}), 400

    file = request.files['file']

    try:
        # Backup erstellen, falls Datei existiert
        if os.path.exists(FACILITY_MODEL_PATH):
            timestamp = int(datetime.now().timestamp())
            backup_name = f"anlage_backup_{timestamp}.glb"
            shutil.copy(FACILITY_MODEL_PATH, os.path.join(FACILITY_BACKUP_DIR, backup_name))

        # Neue Datei speichern
        file.save(FACILITY_MODEL_PATH)
        return jsonify({"status": "success"})
    except Exception as e:
        print(f"Fehler beim Speichern des Modells: {e}")
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/facility/audit_report')
@login_required
def serve_facility_audit_report():
    """Generiert den Audit-Bericht für Struktur UND Inventar."""

    # 1. Struktur laden
    structure_data = {}
    if os.path.exists(FACILITY_STRUCTURE_FILE):
        with open(FACILITY_STRUCTURE_FILE, 'r', encoding='utf-8') as f:
            structure_data = json.load(f)

    # 1b. Wiki laden (für Bereich + Verantwortlicher)
    wiki_data = {}
    if os.path.exists(FACILITY_WIKI_FILE):
        with open(FACILITY_WIKI_FILE, 'r', encoding='utf-8') as f:
            wiki_data = json.load(f)

    # 2. Hardware/Lager laden
    te_data = load_test_equipment_data()
    trolleys = te_data.get('trolleys', {}) if te_data else {}
    storage = te_data.get('storage_units', {}) if te_data else {}

    # 3. Aktive Wartungsdefinitionen laden und nach facilityUnitId gruppieren
    defs_by_unit = {}
    if os.path.exists(MAINTENANCE_DEFINITIONS_FILE):
        with open(MAINTENANCE_DEFINITIONS_FILE, 'r', encoding='utf-8') as f:
            all_defs = json.load(f)
        for d in all_defs:
            if d.get('active') and d.get('facilityUnitId'):
                uid = d['facilityUnitId']
                defs_by_unit.setdefault(uid, []).append({
                    'id': d.get('id', ''),
                    'task': d.get('task', ''),
                    'topic': d.get('topic', ''),
                    'intervalValue': d.get('intervalValue', ''),
                    'intervalUnit': d.get('intervalUnit', '')
                })

    # 4. Flow-Map aufbauen
    flow_map = {}

    def get_flow_entry(nid):
        if nid not in flow_map: flow_map[nid] = {'in': [], 'out': [], 'assoc': []}
        return flow_map[nid]

    for flow in structure_data.get('fluesse', []):
        if flow.get('associatedUnitId'):
            get_flow_entry(flow['associatedUnitId'])['assoc'].append(flow)
        for segment in flow.get('path', []):
            get_flow_entry(segment['from'])['out'].append(flow)
            get_flow_entry(segment['to'])['in'].append(flow)

    # 5. Helper zum Flachklopfen
    def flatten_tree(nodes, level=0, prefix=""):
        result = []
        for i, node in enumerate(nodes):
            current_index = f"{prefix}{i + 1}"
            f_data = flow_map.get(node.get('id'), {'in': [], 'out': [], 'assoc': []})
            node_wiki = wiki_data.get(node.get('id', ''), {})

            # Hardware-Liste bereinigen (JSON -> Label)
            raw_hardware = node.get('hardware', [])
            clean_hardware = []
            for hw in raw_hardware:
                try:
                    if hw.strip().startswith('{'):
                        hw_obj = json.loads(hw)
                        clean_hardware.append(hw_obj.get('label', hw))
                    else:
                        clean_hardware.append(hw)
                except:
                    clean_hardware.append(hw)

            result.append({
                'index_str': current_index,
                'level': level,
                'name': node.get('name', 'Unbenannt'),
                'bereich': node_wiki.get('bereich', ''),
                'verantwortlicher': node_wiki.get('verantwortlicher', ''),
                'hardware': clean_hardware,
                'storageLink': node.get('storageLink'),
                'details': node.get('details', {}),
                'flows_in': f_data['in'],
                'flows_out': f_data['out'],
                'flows_assoc': f_data['assoc'],
                'maintenance_plans': defs_by_unit.get(node.get('id'), [])
            })

            if 'children' in node:
                result.extend(flatten_tree(node['children'], level + 1, current_index + "."))
        return result

    flat_data_units = flatten_tree(structure_data.get('betriebseinheiten', []))
    flat_data_buildings = flatten_tree(structure_data.get('gebaeude', []))

    return render_template_string(
        FACILITY_REPORT_TEMPLATE,
        flat_data_units=flat_data_units,
        flat_data_buildings=flat_data_buildings,
        trolleys=trolleys,
        storage=storage,
        date=datetime.now().strftime("%d.%m.%Y %H:%M"),
        count_units=len(flat_data_units),
        count_buildings=len(flat_data_buildings)
    )

@app.route('/api/generate_events_from_template/<path:engine_type>/<string:ts_iso_date>')
@login_required
def api_generate_events_from_template(engine_type, ts_iso_date):
    try:
        decoded_date_str = unquote(ts_iso_date)
        ts_datetime = datetime.fromisoformat(decoded_date_str.replace('Z', '+00:00'))

    except ValueError:
        # Dieser Fehler wird ausgelöst, wenn das Datum nach dem Dekodieren immer noch ungültig ist
        return jsonify({"error": "Ungültiges TS-Datum nach Dekodierung."}), 400

    # Der restliche Code ist perfekt und kann so bleiben
    process_templates = load_process_templates()
    template = find_best_template_for_engine(engine_type, process_templates)

    # Sicherstellen, dass timeline_events existiert, um Fehler zu vermeiden
    template_events = template.get("timeline_events", [])
    if not template_events:
        print(f"WARNUNG: Kein 'timeline_events'-Array für Engine Type '{engine_type}' (oder im Default) gefunden.")

    dynamic_events = []
    current_date = ts_datetime.date()
    current_shift = 'AM' if ts_datetime.hour < 14 else 'PM'

    for event_def in template_events:
        duration = event_def.get("duration_shifts", 1)
        start_date_loop = current_date
        start_shift_loop = current_shift

        end_date_loop = start_date_loop
        end_shift_loop = start_shift_loop
        for _ in range(duration - 1):
            if end_shift_loop == 'AM':
                end_shift_loop = 'PM'
            else:
                end_shift_loop = 'AM'
                end_date_loop += timedelta(days=1)

        dynamic_events.append({
            'title': event_def.get("name", "Unknown"),
            'start': start_date_loop.strftime("%Y-%m-%d"),
            'start_shift': start_shift_loop,
            'end': end_date_loop.strftime("%Y-%m-%d"),
            'end_shift': end_shift_loop,
            'color': event_def.get("color", "grey")
        })

        current_date = end_date_loop
        current_shift = end_shift_loop
        if current_shift == 'AM':
            current_shift = 'PM'
        else:
            current_shift = 'AM'
            current_date += timedelta(days=1)

    return jsonify(dynamic_events)


@app.route('/api/hardware/set_status', methods=['POST'])
@login_required
def api_set_hardware_status():
    data = request.json
    if not data or 'status' not in data:
        return jsonify({"error": "Fehlende Daten: status ist erforderlich."}), 400

    ids_to_update = set(data.get('ids', []))
    sns_to_update = set(data.get('serial_numbers', []))
    new_status = data.get('status')

    if not ids_to_update and not sns_to_update:
        return jsonify({"message": "Keine IDs oder Seriennummern zum Aktualisieren angegeben."}), 200

    with _get_file_lock(TEST_EQUIPMENT_DATA_FILE):
        hardware_data = _load_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, default_val=None)
        if not isinstance(hardware_data, dict):
            return jsonify({"error": "Hardware-Daten konnten nicht geladen werden."}), 500

        inventory = hardware_data.get('inventory', {})
        changes_made = False
        updated_count = 0

        for pn, part in inventory.items():
            for serial_entry in part.get('serials', []):
                is_match = (
                    (serial_entry.get('id') and serial_entry['id'] in ids_to_update) or
                    (serial_entry.get('sn') and serial_entry['sn'] in sns_to_update)
                )
                if is_match and serial_entry.get('status') != new_status:
                    print(f"INFO: Ändere Status für {serial_entry.get('sn')} ({serial_entry.get('id')}) zu '{new_status}'.")
                    serial_entry['status'] = new_status
                    changes_made = True
                    updated_count += 1

        if not changes_made:
            return jsonify({"message": "Keine Statusänderungen erforderlich (Status war bereits korrekt)."}), 200

        recalculate_kit_status(hardware_data)
        success = _write_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, hardware_data)

    if success:
        return jsonify({"message": f"{updated_count} Hardware-Status aktualisiert."}), 200
    else:
        return jsonify({"error": "Fehler beim Speichern der Hardware-Daten."}), 500


@app.route('/api/maintenance/save_orders', methods=['POST'])
@login_required
def api_save_maintenance_orders():
    data = request.json
    if not data or 'orders' not in data:
        return jsonify({"error": "Keine Orders empfangen"}), 400

    if save_maintenance_orders(data['orders']):
        # Sync Logik für Hardware bleibt hier hängen, da sie an Orders hängt
        sync_maintenance_links_to_hardware(data['orders'])
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/maintenance/save_definitions', methods=['POST'])
@login_required
def api_save_maintenance_definitions():
    data = request.json
    if not data or 'definitions' not in data:
        return jsonify({"error": "Keine Definitionen empfangen"}), 400

    if save_maintenance_definitions(data['definitions']):
        try:
            m_data = load_maintenance_data()  # Lädt Orders, Defs, Companies
            orders = m_data.get('orders', [])
            sync_maintenance_links_to_hardware(orders)
            print("INFO: Hardware-Links nach Definitions-Update synchronisiert.")

        except Exception as e:
            print(f"WARNUNG: Sync nach Definitions-Speichern fehlgeschlagen: {e}")

        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/maintenance/definitions/<def_id>', methods=['PUT'])
@login_required
def api_upsert_maintenance_definition(def_id):
    """Upsert a single definition — reads current file first to prevent concurrent-user overwrites."""
    def_data = request.json
    if not def_data:
        return jsonify({"error": "Keine Daten empfangen"}), 400
    with _get_file_lock(MAINTENANCE_DEFINITIONS_FILE):
        definitions = _load_json_file_unlocked(MAINTENANCE_DEFINITIONS_FILE)
        idx = next((i for i, d in enumerate(definitions) if str(d.get('id')) == str(def_id)), None)
        if idx is not None:
            definitions[idx] = def_data
        else:
            definitions.append(def_data)
        success = _write_json_file_unlocked(MAINTENANCE_DEFINITIONS_FILE, definitions)
    if success:
        try:
            m_data = load_maintenance_data()
            sync_maintenance_links_to_hardware(m_data.get('orders', []))
        except Exception as e:
            print(f"WARNUNG: Sync nach Definition-Upsert fehlgeschlagen: {e}")
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/maintenance/definitions/<def_id>', methods=['DELETE'])
@login_required
def api_delete_maintenance_definition(def_id):
    """Delete a single definition by ID — reads current file first."""
    with _get_file_lock(MAINTENANCE_DEFINITIONS_FILE):
        definitions = _load_json_file_unlocked(MAINTENANCE_DEFINITIONS_FILE)
        definitions = [d for d in definitions if str(d.get('id')) != str(def_id)]
        success = _write_json_file_unlocked(MAINTENANCE_DEFINITIONS_FILE, definitions)
    if success:
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Löschen fehlgeschlagen"}), 500


@app.route('/api/maintenance/save_companies', methods=['POST'])
@login_required
def api_save_maintenance_companies():
    data = request.json
    if not data or 'companies' not in data:
        return jsonify({"error": "Keine Firmen empfangen"}), 400

    if save_maintenance_companies(data['companies']):
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/maintenance/orders', methods=['PUT'])
@login_required
def api_upsert_maintenance_order():
    """Upsert (create or update) a single order. Reads current file state first to prevent overwrites."""
    order_data = request.json
    if not order_data or 'id' not in order_data:
        return jsonify({"error": "Order-Daten oder ID fehlen"}), 400
    order_id = str(order_data['id'])
    with _get_file_lock(MAINTENANCE_ORDERS_FILE):
        orders = _load_json_file_unlocked(MAINTENANCE_ORDERS_FILE)
        idx = next((i for i, o in enumerate(orders) if str(o.get('id')) == order_id), None)
        if idx is not None:
            orders[idx] = order_data
        else:
            orders.append(order_data)
        success = _write_json_file_unlocked(MAINTENANCE_ORDERS_FILE, orders)
    if success:
        sync_maintenance_links_to_hardware(orders)
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/maintenance/orders/bulk-delete', methods=['POST'])
@login_required
def api_bulk_delete_maintenance_orders():
    """Delete multiple orders by ID list. Reads current file state first."""
    data = request.json
    if not data or 'ids' not in data:
        return jsonify({"error": "IDs fehlen"}), 400
    ids_to_delete = {str(i) for i in data['ids']}
    with _get_file_lock(MAINTENANCE_ORDERS_FILE):
        orders = _load_json_file_unlocked(MAINTENANCE_ORDERS_FILE)
        new_orders = [o for o in orders if str(o.get('id')) not in ids_to_delete]
        success = _write_json_file_unlocked(MAINTENANCE_ORDERS_FILE, new_orders)
    if success:
        sync_maintenance_links_to_hardware(new_orders)
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Löschen fehlgeschlagen"}), 500


@app.route('/api/maintenance/orders/batch', methods=['PUT'])
@login_required
def api_batch_upsert_maintenance_orders():
    """Upsert multiple orders atomically. Reads current file state first."""
    data = request.json
    if not data or 'orders' not in data:
        return jsonify({"error": "Orders fehlen"}), 400
    updates = {str(o['id']): o for o in data['orders'] if 'id' in o}
    with _get_file_lock(MAINTENANCE_ORDERS_FILE):
        orders = _load_json_file_unlocked(MAINTENANCE_ORDERS_FILE)
        new_orders = [updates.pop(str(o.get('id')), o) for o in orders]
        new_orders.extend(updates.values())
        success = _write_json_file_unlocked(MAINTENANCE_ORDERS_FILE, new_orders)
    if success:
        sync_maintenance_links_to_hardware(new_orders)
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/maintenance/orders/<path:order_id>', methods=['DELETE'])
@login_required
def api_delete_maintenance_order(order_id):
    """Delete a single order by ID. Reads current file state first."""
    with _get_file_lock(MAINTENANCE_ORDERS_FILE):
        orders = _load_json_file_unlocked(MAINTENANCE_ORDERS_FILE)
        new_orders = [o for o in orders if str(o.get('id')) != str(order_id)]
        if len(new_orders) == len(orders):
            return jsonify({"error": "Order nicht gefunden"}), 404
        success = _write_json_file_unlocked(MAINTENANCE_ORDERS_FILE, new_orders)
    if success:
        sync_maintenance_links_to_hardware(new_orders)
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Löschen fehlgeschlagen"}), 500


@app.route('/api/hardware_assignment/<string:timeline_id>', methods=['POST'])
@login_required
def api_save_hardware_assignment(timeline_id):
    """Speichert eine manuelle Hardware-Kit-Zuweisung für ein Event."""
    data = request.json
    if not data or 'event_id' not in data:
        return jsonify({"error": "event_id fehlt"}), 400

    event_id = data['event_id']
    kit_id = data.get('kit_id')  # None = Zuweisung entfernen

    assignments = load_hardware_assignments(timeline_id)

    if kit_id:
        assignments[event_id] = {'kit_id': kit_id, 'is_manual': True}
    else:
        assignments.pop(event_id, None)

    if save_hardware_assignments(timeline_id, assignments):
        return jsonify({"status": "ok"})
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/api/hardware/kits_list', methods=['GET'])
@login_required
def api_get_hardware_kits_list():
    """Gibt eine Liste aller Hardware-Kits (BOMs) mit Code, Name und kompatiblen Engine-Typen zurück."""
    data = load_test_equipment_data()
    if data is None:
        return jsonify([])

    boms = data.get('boms', {})
    mappings = data.get('mappings', {})

    # kit_id → Liste der Engine-Typen, die dieses Kit nutzen
    kit_to_engines: Dict[str, List[str]] = {}
    for engine_type, kit_ids in mappings.items():
        for kid in (kit_ids or []):
            kit_to_engines.setdefault(kid, []).append(engine_type)

    result = []
    for kit_id, kit in boms.items():
        result.append({
            'id': kit_id,
            'name': kit.get('name', ''),
            'code': kit.get('code', ''),
            'engine_types': sorted(kit_to_engines.get(kit_id, [])),
        })
    result.sort(key=lambda x: (x.get('code') or 'ZZZ', x.get('name', '')))
    return jsonify(result)


@app.route('/api/test_equipment/data', methods=['GET'])
@login_required
def api_get_test_equipment_data_json():
    """Gibt eine Liste aller physischen Geräte zurück (ID, Label, PN, SN, ProductType)."""
    data = load_test_equipment_data()
    if data is None: return jsonify([])

    inventory = data.get('inventory', {})
    hardware_list = []

    for pn, item in inventory.items():
        part_name = item.get('name', 'Unbekannt')
        # NEU: Product Type holen (kann Array oder String sein)
        p_type = item.get('productType', [])

        for serial_entry in item.get('serials', []):
            hw_id = serial_entry.get('id', 'temp_missing_id')
            sn = serial_entry.get('sn', 'N/A')
            display_string = f"{part_name} / {pn} / {sn}"

            hardware_list.append({
                "id": hw_id,
                "label": display_string,
                "sn": sn,
                "pn": pn,
                "productType": p_type
            })

    hardware_list.sort(key=lambda x: x['label'])
    return jsonify(hardware_list)


@app.route('/api/maintenance/upload', methods=['POST'])
@login_required
def api_maintenance_upload():
    if 'file' not in request.files:
        return jsonify({"error": "Keine Datei."}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({"error": "Leerer Dateiname."}), 400

    try:
        os.makedirs(MAINTENANCE_UPLOADS_DIR, exist_ok=True)
        filename = secure_filename(file.filename)
        # Um Duplikate zu vermeiden, Timestamp voranstellen
        unique_filename = f"{int(datetime.now().timestamp())}_{filename}"
        file.save(os.path.join(MAINTENANCE_UPLOADS_DIR, unique_filename))

        # Wir geben eine Data-URL zurück für die Vorschau (in Produktion würde man den Pfad speichern)
        # Hier simulieren wir die Data-URL Rückgabe vereinfacht durch den Pfad,
        # da wir im HTML FileReader nutzen. Für echte Persistenz muss man den Pfad speichern.
        return jsonify({"filename": unique_filename, "path": f"/maintenance/files/{unique_filename}"}), 200
    except Exception as e:
        print(f"Upload Fehler: {e}")
        return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/maintenance/files/<path:filename>')
@login_required
def serve_maintenance_file(filename):
    return send_from_directory(MAINTENANCE_UPLOADS_DIR, filename)


@app.route('/api/maintenance/upload/<path:filename>', methods=['DELETE'])
@login_required
def api_maintenance_delete_file(filename):
    safe_name = secure_filename(filename)
    file_path = os.path.join(MAINTENANCE_UPLOADS_DIR, safe_name)
    if not os.path.exists(file_path):
        return jsonify({"error": "Datei nicht gefunden."}), 404
    try:
        os.remove(file_path)
        return jsonify({"ok": True}), 200
    except Exception as e:
        print(f"Fehler beim Löschen der Datei: {e}")
        return jsonify({"error": "Löschen fehlgeschlagen."}), 500


@app.route('/api/maintenance/logbook/upload', methods=['POST'])
@login_required
def api_logbook_upload():
    if 'file' not in request.files:
        return jsonify({"error": "Keine Datei."}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({"error": "Leerer Dateiname."}), 400
    try:
        os.makedirs(MAINTENANCE_LOGBOOK_UPLOADS_DIR, exist_ok=True)
        filename = secure_filename(file.filename)
        unique_filename = f"{int(datetime.now().timestamp())}_{filename}"
        file.save(os.path.join(MAINTENANCE_LOGBOOK_UPLOADS_DIR, unique_filename))
        return jsonify({"filename": unique_filename, "path": f"/maintenance/logbook/files/{unique_filename}"}), 200
    except Exception as e:
        print(f"Logbuch-Upload Fehler: {e}")
        return jsonify({"error": "Speichern fehlgeschlagen"}), 500


@app.route('/maintenance/logbook/files/<path:filename>')
@login_required
def serve_logbook_file(filename):
    return send_from_directory(MAINTENANCE_LOGBOOK_UPLOADS_DIR, filename)


@app.route('/api/maintenance/logbook/upload/<path:filename>', methods=['DELETE'])
@login_required
def api_logbook_delete_file(filename):
    safe_name = secure_filename(filename)
    file_path = os.path.join(MAINTENANCE_LOGBOOK_UPLOADS_DIR, safe_name)
    if not os.path.exists(file_path):
        return jsonify({"error": "Datei nicht gefunden."}), 404
    try:
        os.remove(file_path)
        return jsonify({"ok": True}), 200
    except Exception as e:
        print(f"Fehler beim Löschen der Logbuch-Datei: {e}")
        return jsonify({"error": "Löschen fehlgeschlagen."}), 500


@app.route('/api/maintenance/save', methods=['POST'])
@login_required
def api_save_maintenance():
    data = request.json

    # 1. Wartungsdaten speichern
    if save_maintenance_data_to_file(data):

        # 2. NEU: Verknüpfungen zur Hardware aktualisieren (im Hintergrund)
        # Wir übergeben die Liste der Orders an die Sync-Funktion
        sync_maintenance_links_to_hardware(data.get('orders', []))

        return jsonify({"status": "success"}), 200
    else:
        return jsonify({"status": "error"}), 500


@app.route('/maintenance')
@login_required
def serve_maintenance_page():
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()
    user_id = session.get('user_id', '').lower()
    user_roles = session.get('roles', [])
    data = load_maintenance_data()
    orders = data.get('orders', [])
    permissions_map = {}
    for order in orders:
        if order.get('status') in ['Review Ready'] or (order.get('type') == 'I' and order.get('priority') == 'OPEN'):
            permissions_map[order.get('id')] = can_user_review(order, user_id, user_roles)
        else:
            permissions_map[order.get('id')] = False

    companies_data = data.get('companies', [])
    all_hardware_assets = api_get_test_equipment_data_json().get_json()

    return render_template_string(
        MAINTENANCE_PAGE_HTML,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        active_page='maintenance',
        url_for=url_for,
        session=session,
        orders_json=json.dumps(data.get('orders', [])),
        definitions_json=json.dumps(data.get('definitions', [])),
        companies_json=json.dumps(companies_data),
        permissions_json=json.dumps(permissions_map),
        hardware_assets_json=json.dumps(all_hardware_assets)
    )

def ensure_hardware_ids(data):
    """
    Durchläuft das Inventar und gibt jedem Serial-Item eine UUID,
    falls es noch keine hat.
    """
    inventory = data.get('inventory', {})
    changes = False

    for pn, part in inventory.items():
        for serial in part.get('serials', []):
            if 'id' not in serial or not serial['id']:
                # Neue ID generieren
                serial['id'] = f"hw_{uuid.uuid4().hex[:12]}"  # Kurze UUID reicht oft
                changes = True
    return changes


@app.route('/api/test_equipment/save', methods=['POST'])
@login_required
def api_save_test_equipment():
    print("\n=== DEBUG: API /api/test_equipment/save aufgerufen ===")

    try:
        data = request.json
    except Exception as e:
        return jsonify({"status": "error", "message": "Invalid JSON format"}), 400

    if data is None:
        return jsonify({"status": "error", "message": "Keine Daten empfangen"}), 400

    client_hash = data.pop('version_hash', None)

    # Preprocessing: reine In-Memory-Operationen, kein File I/O → außerhalb des Locks
    # 0. completeness_log aus Trolleys/Storage entfernen (liegt in separater Datei)
    for container_dict in [data.get('trolleys', {}), data.get('storage_units', {})]:
        for container in container_dict.values():
            container.pop('completeness_log', None)

    # 1. IDs sicherstellen (Hardware)
    ensure_hardware_ids(data)

    # 2. Kits migrieren (Namen -> UUIDs)
    ensure_kit_ids_and_migration(data)

    # 3. Status serverseitig berechnen (Single Source of Truth)
    recalculate_kit_status(data)

    # Bug 1+2 Fix: Check und Write atomar unter demselben Lock — verhindert TOCTOU
    # (zwei gleichzeitige Requests bestehen sonst beide den Hash-Check und überschreiben sich)
    with _get_file_lock(TEST_EQUIPMENT_DATA_FILE):
        if client_hash:
            current_data = _load_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, default_val=None)
            if isinstance(current_data, dict):
                current_hash = compute_hardware_data_hash(current_data)
                if current_hash != client_hash:
                    print(f"!!! KONFLIKT: Client-Hash {client_hash[:8]} != Server-Hash {current_hash[:8]}")
                    return jsonify({"status": "conflict", "message": "Daten wurden von einem anderen Benutzer geändert."}), 409
        if not _write_json_file_unlocked(TEST_EQUIPMENT_DATA_FILE, data):
            return jsonify({"status": "error", "message": "Fehler beim Schreiben der Hardware-Datei."}), 500

    # Syncs außerhalb des Locks — jeder Sync holt seinen eigenen Lock intern
    # --- SYNC 1: Wartungspläne ---
    sync_hardware_names_to_definitions(data)

    # --- SYNC 2: Maintenance-Pillen wiederherstellen ---
    try:
        maintenance_data = load_maintenance_data()
        orders = maintenance_data.get('orders', [])
        sync_maintenance_links_to_hardware(orders)
    except Exception as e:
        print(f"!!! FEHLER beim Wiederherstellen der Links: {e}")

    # --- SYNC 3: Facility Struktur & Trolley ---
    sync_hardware_names_to_facility(data)
    sync_trolleys_and_storage(data)

    # Bug 6 Fix: Hash erst nach allen Syncs aus der Datei lesen — sync_maintenance_links_to_hardware
    # und sync_trolleys_and_storage schreiben die Datei ggf. nochmals und ändern den Hash.
    # compute_hardware_data_hash(data) wäre hier veraltet und würde beim nächsten Save
    # einen falschen 409 auslösen, auch ohne echten Konflikt.
    new_hash = get_current_hardware_file_hash()
    return jsonify({"status": "success", "message": "Hardware gespeichert, berechnet & synchronisiert.", "version_hash": new_hash}), 200

@app.route('/test_equipment')
@login_required
def serve_test_equipment_page():
    user_name = get_current_username_from_session() or "Unbekannt"

    # Initialen berechnen
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    te_data = load_test_equipment_data()

    if te_data is None:
        te_data = {"inventory": {}, "boms": {}, "mappings": {}, "trolleys": {}, "storage_units": {}}

    if ensure_hardware_ids(te_data):
        print("INFO: Hardware-IDs beim Seitenaufruf generiert und persistent gespeichert.")
        save_test_equipment_data_to_file(te_data)

    version_hash = compute_hardware_data_hash(te_data)

    return render_template_string(
        TEST_EQUIPMENT_PAGE_HTML,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        active_page='test_equipment',
        url_for=url_for,
        session=session,
        inventory_json=json.dumps(te_data.get('inventory', {})),
        boms_json=json.dumps(te_data.get('boms', {})),
        mappings_json=json.dumps(te_data.get('mappings', {})),
        trolleys_json=json.dumps(te_data.get('trolleys', {})),
        storage_units_json=json.dumps(te_data.get('storage_units', {})),
        employees_json=json.dumps(sorted([e.get('name', '') for e in ALL_EMPLOYEES if e.get('name')])),
        completeness_config_json=json.dumps(load_completeness_config()),
        hardware_version_hash=version_hash
    )


@app.route('/livestatus')
def serve_project_monitor():
    """Zeigt das eigenständige Projekt-Monitor Dashboard ohne Sidebar."""
    project_list = load_project_data_monitor()
    return render_template_string(PROJECT_MONITOR_HTML, projects=project_list, url_for=url_for)


@app.route('/ordersystem')
@login_required
def serve_consumable_order_page():
    """Rendert die Hauptseite für das Bestellsystem."""
    user_name = get_current_username_from_session() or "Unbekannt"
    orders_data, templates_data = order_system_load_data()
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    return render_template_string(
        CONSUMABLE_ORDER_PAGE_HTML,
        current_user_display_name=user_name,
        current_user_initials=user_initials,  # <-- HINZUGEFÜGT
        orders_json=json.dumps(orders_data),
        templates_json=json.dumps(templates_data),
        active_page='ordersystem'
    )


@app.route('/api/order_system/add_request', methods=['POST'])
@login_required
def api_add_order_request():
    # Jeder darf bestellen!
    new_order = request.json
    if not new_order or 'id' not in new_order or 'name' not in new_order:
        return jsonify({"error": "Ungültige Bestellung"}), 400

    # Daten laden
    orders_data, templates_data = order_system_load_data()

    # Sicherstellen, dass Status 'requested' ist (Security)
    new_order['status'] = 'requested'

    # Anhängen
    orders_data.append(new_order)

    # Speichern
    order_system_save_data({'orders': orders_data, 'templates': templates_data})

    return jsonify({"status": "success", "message": "Bestellung aufgegeben."}), 200

@app.route('/api/order_system/save_data', methods=['POST'])
@login_required
def order_system_save_data_endpoint():
    user_roles = session.get('roles', [])

    # Nur Admins und ConsumableManager dürfen die Daten vollständig überschreiben.
    if Roles.ADMIN not in user_roles and Roles.CONSUMABLEMANAGER not in user_roles:
        # Hier könnte man eine komplexere Logik einbauen, die prüft,
        # ob der User nur seine eigenen "requested" Orders hinzugefügt hat.
        # Für den Anfang ist eine strikte Sperre sicherer.
        return jsonify({"error": "Keine Berechtigung zum Speichern der Bestelldaten."}), 403

    data = request.get_json()
    order_system_save_data(data)
    return jsonify({"status": "success", "message": "Daten erfolgreich gespeichert."})


@app.route('/admin/download_db_folder/<path:folderpath>')
@admin_required
def download_db_folder(folderpath):
    """Packt einen Ordner aus dem 'db'-Verzeichnis in ein ZIP-Archiv und sendet es zum Download."""
    base_dir = os.path.abspath(DB_ROOT_PATH)

    # Der spezielle Wert 'ROOT' steht für das gesamte 'db'-Verzeichnis
    if folderpath == 'ROOT':
        full_path = base_dir
    else:
        full_path = os.path.abspath(os.path.join(base_dir, folderpath))

    # Security Check
    if not full_path.startswith(base_dir) or not os.path.isdir(full_path):
        abort(404, description="Ordner nicht gefunden oder Zugriff verweigert.")

    # Erstelle eine ZIP-Datei im Speicher
    memory_file = io.BytesIO()
    with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
        for root, dirs, files in os.walk(full_path):
            for file in files:
                file_path = os.path.join(root, file)
                # Erstelle den relativen Pfad für die Struktur innerhalb der ZIP-Datei
                arcname = os.path.relpath(file_path, full_path)
                zf.write(file_path, arcname)

    memory_file.seek(0)

    # Dateiname für den Download festlegen
    if folderpath == 'ROOT':
        download_filename = 'db_backup.zip'
    else:
        download_filename = f"{os.path.basename(folderpath)}.zip"

    return send_file(
        memory_file,
        mimetype='application/zip',
        as_attachment=True,
        download_name=download_filename
    )


@app.route('/api/upload_certificate', methods=['POST'])
@admin_required
def api_upload_certificate():
    if 'certificate_file' not in request.files:
        return jsonify({"error": "Keine Datei im Upload gefunden."}), 400

    file = request.files['certificate_file']
    employee_id = request.form.get('employee_id')
    merkmal_name = request.form.get('merkmal_name')  # NEU: Merkmal wird mitgesendet

    if file.filename == '' or not employee_id or not merkmal_name:
        return jsonify({"error": "Fehlende Informationen (Datei, Mitarbeiter-ID oder Merkmal)."}), 400

    try:
        filename = secure_filename(file.filename)
        # NEU: Speichere die Datei in einem Unterordner /uploads/ID/
        user_upload_folder = os.path.join(CERTIFICATES_UPLOADS_FOLDER_PATH, employee_id)
        os.makedirs(user_upload_folder, exist_ok=True)

        full_path = os.path.join(user_upload_folder, filename)
        file.save(full_path)

        # NEU: Metadaten in ID.json aktualisieren
        metadata = get_certificate_metadata(employee_id)
        metadata = [m for m in metadata if m.get('filename') != filename]  # Alte Einträge für dieselbe Datei entfernen
        metadata.append({
            "filename": filename,
            "merkmal": merkmal_name,  # NEU: Merkmal speichern
            "uploaded_at": datetime.now().isoformat(),
            "uploaded_by": get_current_username_from_session()
        })
        save_certificate_metadata(employee_id, metadata)

        return jsonify({"message": "Datei erfolgreich hochgeladen.", "filename": filename}), 200

    except Exception as e:
        print(f"FEHLER beim Zertifikat-Upload: {e}")
        return jsonify({"error": "Ein interner Fehler ist beim Speichern der Datei aufgetreten."}), 500


@app.route('/download_certificate/<string:employee_id>/<path:filename>')
@login_required
def download_certificate_route(employee_id, filename):
    # Security Check wurde verbessert
    safe_employee_id = secure_filename(employee_id)
    safe_filename = secure_filename(filename)

    base_dir = os.path.abspath(CERTIFICATES_UPLOADS_FOLDER_PATH)
    full_path = os.path.abspath(os.path.join(base_dir, safe_employee_id, safe_filename))

    if not full_path.startswith(base_dir):
        abort(403)

    if not os.path.isfile(full_path):
        abort(404)

    return send_file(full_path, as_attachment=True)


@app.route('/api/admin/create_timeline', methods=['POST'])
@admin_required
def api_create_timeline():
    data = request.json
    required_fields = ['name', 'id', 'type']
    if not data or not all(field in data for field in required_fields):
        return jsonify({"error": "Fehlende Daten: Name, ID und Typ sind erforderlich."}), 400

    raw_id = data.get('id')
    if not raw_id:
        return jsonify({"error": "Die ID darf nicht leer sein."}), 400
    new_id = raw_id.strip().lower().replace(' ', '_')
    if not new_id.isidentifier():  # Prüft, ob es ein gültiger Bezeichner ist
        return jsonify({
            "error": "Die ID darf nur Buchstaben, Zahlen und Unterstriche enthalten und nicht mit einer Zahl beginnen."}), 400

    config = load_app_config()
    timelines = config.get('timelines', [])

    # Prüfen, ob die ID bereits existiert
    if any(tl['id'] == new_id for tl in timelines):
        return jsonify({"error": f"Eine Timeline mit der ID '{new_id}' existiert bereits."}), 409

    # Basis-Struktur für die neue Timeline
    new_timeline_config = {
        "id": new_id,
        "name": data['name'].strip(),
        "type": data['type']
    }

    # Füge die Konfigurationsoptionen hinzu, die für BEIDE Typen gelten
    new_timeline_config['visible_test_cells'] = data.get('visible_test_cells', [])
    new_timeline_config['assignment_groups'] = data.get('assignment_groups', [])

    # Spezifische Konfiguration je nach Typ
    if data['type'] == 'editable':
        base_file_path = os.path.join('db', f"{new_id}_timeline")
        new_timeline_config['data_file'] = f"{base_file_path}_projects.csv"
        new_timeline_config['assignment_rules_file'] = f"{base_file_path}_rules.csv"
        new_timeline_config['manual_assignments_file'] = f"{base_file_path}_assignments.csv"
        new_timeline_config['sap_filter'] = data.get('sap_filter', {})
        new_timeline_config['available_hardware_kits'] = data.get('available_hardware_kits', [])
    else:  # Für 'static' Timelines
        # Statische Timelines haben keine eigene Projektdatei, sondern referenzieren andere.
        # Dies muss weiterhin manuell in der config.json geschehen, aber wir erstellen einen leeren Platzhalter.
        new_timeline_config['data_files'] = data.get('data_files', [])

    try:
        # 1. Konfiguration aktualisieren und speichern
        timelines.append(new_timeline_config)
        config['timelines'] = timelines
        with open(os.path.join(DB_ROOT_PATH, 'config.json'), 'w', encoding='utf-8') as f:
            json.dump(config, f, indent=2, ensure_ascii=False)

        # 2. Leere CSV-Dateien erstellen, falls 'editable'
        if data['type'] == 'editable':
            # Projektdaten-Datei
            with open(new_timeline_config['data_file'], 'w', newline='', encoding='utf-8-sig') as f:
                csv.writer(f).writerow(
                    ['name', 'engine_type', 'project_code', 'engine_serial', 'customer', 'BE', 'TS', 'TE', 'SA',
                     'active'])

            # Regel-Datei
            with open(new_timeline_config['assignment_rules_file'], 'w', newline='', encoding='utf-8-sig') as f:
                csv.writer(f).writerow(ASSIGNMENT_RULES_FIELDNAMES)

            # Manuelle Zuweisungs-Datei
            with open(new_timeline_config['manual_assignments_file'], 'w', newline='', encoding='utf-8-sig') as f:
                csv.writer(f).writerow(ASSIGNED_EMPLOYEES_FIELDNAMES)

        return jsonify({"message": f"Timeline '{new_timeline_config['name']}' wurde erfolgreich erstellt."}), 201

    except Exception as e:
        # Hier wäre eine Rollback-Logik ideal, aber für den Anfang reicht eine Fehlermeldung
        print(f"FEHLER beim Erstellen der Timeline: {e}")
        traceback.print_exc()
        return jsonify(
            {"error": "Ein Fehler ist beim Erstellen der Timeline und der zugehörigen Dateien aufgetreten."}), 500


@app.route('/api/admin/delete_timeline/<string:timeline_id>', methods=['DELETE'])
@admin_required
def api_delete_timeline(timeline_id):
    """Löscht eine Timeline aus der Konfiguration und die zugehörigen Dateien."""
    config = load_app_config()
    timelines = config.get('timelines', [])

    timeline_to_delete = next((tl for tl in timelines if tl['id'] == timeline_id), None)

    if not timeline_to_delete:
        return jsonify({"error": "Timeline nicht gefunden."}), 404

    try:
        # 1. Entferne die Timeline aus der Konfigurationsliste
        timelines = [tl for tl in timelines if tl['id'] != timeline_id]
        config['timelines'] = timelines

        # Schreibe die aktualisierte config.json
        with open(os.path.join(DB_ROOT_PATH, 'config.json'), 'w', encoding='utf-8') as f:
            json.dump(config, f, indent=2, ensure_ascii=False)

        # 2. Lösche die zugehörigen CSV-Dateien, falls es eine 'editable' Timeline war
        if timeline_to_delete.get('type') == 'editable':
            files_to_delete = [
                timeline_to_delete.get('data_file'),
                timeline_to_delete.get('assignment_rules_file'),
                timeline_to_delete.get('manual_assignments_file')
            ]
            for file_path in files_to_delete:
                if file_path and os.path.exists(file_path):
                    try:
                        os.remove(file_path)
                        print(f"INFO: Datei '{file_path}' wurde gelöscht.")
                    except OSError as e:
                        # Logge den Fehler, aber fahre fort, damit der Prozess nicht abbricht
                        print(f"WARNUNG: Datei '{file_path}' konnte nicht gelöscht werden: {e}")

        return jsonify({"message": f"Timeline '{timeline_to_delete.get('name')}' wurde erfolgreich gelöscht."}), 200

    except Exception as e:
        print(f"FEHLER beim Löschen der Timeline '{timeline_id}': {e}")
        traceback.print_exc()
        # Hier wäre ein Rollback der config.json-Änderung ideal, aber für den Moment ist eine Fehlermeldung ausreichend.
        return jsonify({"error": "Ein Fehler ist beim Löschvorgang aufgetreten."}), 500


@app.route('/api/timeline_config/<string:timeline_id>')
@admin_required
def get_timeline_config(timeline_id):
    """Gibt die Konfiguration für eine einzelne Timeline zurück."""
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if timeline_config:
        return jsonify(timeline_config)
    else:
        return jsonify({"error": "Timeline nicht gefunden."}), 404


@app.route('/api/admin/update_timeline/<string:timeline_id>', methods=['PUT'])
@admin_required
def api_update_timeline(timeline_id):
    """Aktualisiert die Konfiguration einer bestehenden Timeline."""
    data = request.json
    if not data:
        return jsonify({"error": "Keine Daten empfangen."}), 400

    config = load_app_config()
    timelines = config.get('timelines', [])

    timeline_to_update_index = -1
    for i, tl in enumerate(timelines):
        if tl['id'] == timeline_id:
            timeline_to_update_index = i
            break

    if timeline_to_update_index == -1:
        return jsonify({"error": "Timeline nicht gefunden."}), 404

    # Aktualisiere die Felder
    # Die ID wird nicht geändert!
    timelines[timeline_to_update_index]['name'] = data.get('name', timelines[timeline_to_update_index]['name'])
    timelines[timeline_to_update_index]['type'] = data.get('type', timelines[timeline_to_update_index]['type'])

    if timelines[timeline_to_update_index]['type'] == 'editable':
        timelines[timeline_to_update_index]['visible_test_cells'] = data.get('visible_test_cells', [])
        timelines[timeline_to_update_index]['assignment_groups'] = data.get('assignment_groups', [])
        timelines[timeline_to_update_index]['sap_filter'] = data.get('sap_filter', {})
        timelines[timeline_to_update_index]['available_hardware_kits'] = data.get('available_hardware_kits', [])

    if timelines[timeline_to_update_index]['type'] == 'static':
        timelines[timeline_to_update_index]['data_files'] = data.get('data_files', [])
        # Entferne 'editable'-spezifische Schlüssel, falls sie noch existieren
        timelines[timeline_to_update_index].pop('assignment_rules_file', None)
        timelines[timeline_to_update_index].pop('manual_assignments_file', None)
        timelines[timeline_to_update_index].pop('visible_test_cells', None)
        timelines[timeline_to_update_index].pop('assignment_groups', None)
        timelines[timeline_to_update_index].pop('sap_filter', None)

    config['timelines'] = timelines

    try:
        with open(os.path.join(DB_ROOT_PATH, 'config.json'), 'w', encoding='utf-8') as f:
            json.dump(config, f, indent=2, ensure_ascii=False)
        return jsonify({"message": f"Timeline '{data.get('name')}' wurde erfolgreich aktualisiert."}), 200
    except Exception as e:
        print(f"FEHLER beim Aktualisieren der config.json: {e}")
        return jsonify({"error": "Konfigurationsdatei konnte nicht gespeichert werden."}), 500

@app.route('/')
@login_required
def index():
    """Leitet den Benutzer direkt zum neuen Dashboard weiter."""
    return redirect(url_for('dashboard_page'))

@app.route('/dashboard')
@login_required
def dashboard_page():
    # 1. Basis-Daten laden
    config = load_app_config()
    timelines = config.get('timelines', [])
    timelines.sort(key=lambda x: x['type'], reverse=True)

    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname: user_initials = (vorname[0] + nachname[0]).upper()
    current_user_id = session.get('user_id', '').lower()

    # 2. Mitarbeiter laden
    if not ALL_EMPLOYEES:
        load_and_prepare_qualifications_data()
    # User-Map für schnellen Namens-Lookup (für Kollegen-Anzeige)
    user_map = {u['id']: u for u in ALL_USERS if u.get('id')}

    # 3. Anwesenheit Heute prüfen
    # Wochentag: kein Eintrag = anwesend, Abwesenheitsmarker = abwesend.
    # Wochenende: kein Eintrag = NICHT anwesend, expliziter leerer Eintrag (Sondereinsatz) = anwesend.
    today_iso = date.today().isoformat()
    today_is_weekend = date.today().weekday() >= 5
    present_employees = []

    for emp in ALL_EMPLOYEES:
        has_today_entry = False
        is_absent_today = False
        has_weekend_work = False
        for week in emp.get('weeks', []):
            for day in week.get('days', []):
                d = day.get('date')
                d_str = d.isoformat() if isinstance(d, date) else str(d)
                if d_str == today_iso:
                    has_today_entry = True
                    ereignis = day.get('ereignis', '').strip()
                    if ereignis:
                        is_absent_today = True
                    elif today_is_weekend:
                        has_weekend_work = True
                    break
        if today_is_weekend:
            if has_weekend_work:
                present_employees.append(emp)
        else:
            if not is_absent_today:
                present_employees.append(emp)

    present_employees_count = len(present_employees)

    # 4. Globale Zuweisungen laden
    global_scheduler.load()
    assignments_today = global_scheduler.get_assignments_for_day(today_iso)

    # 5. "Free Pool" & "Busy List" berechnen
    busy_employee_ids = set()
    for shift_data in assignments_today.values():
        busy_employee_ids.update(shift_data.keys())

    free_employees = []
    busy_employees_details = []

    for emp in present_employees:
        emp_id = emp.get('id', '').lower()
        if emp_id in busy_employee_ids:
            # Details holen (AM oder PM)
            assignment_am = assignments_today.get('AM', {}).get(emp_id)
            assignment_pm = assignments_today.get('PM', {}).get(emp_id)
            details = assignment_am or assignment_pm

            # Text bauen
            proj = details.get('project', 'Unbekannt')
            task = details.get('task', '')
            display_text = f"{proj} ({task})" if task else proj

            busy_employees_details.append({
                "name": emp.get('name'),
                "assignment": display_text
            })
        else:
            free_employees.append({"name": emp.get('name')})

    free_employees.sort(key=lambda x: x['name'])
    busy_employees_details.sort(key=lambda x: x['name'])

    # 6. "Mein Plan Heute" (Persönliche Agenda)
    my_missions = []  # Liste von Dictionaries

    if current_user_id:
        def get_mission_details(details, all_assignments_in_shift):
            if not details: return None

            proj = details.get('project', 'Projekt')
            eng = details.get('engine', '')
            task = details.get('task', '')

            # Redundanz-Check: Wenn Engine im Projektnamen steht, Engine ausblenden?
            # Oder einfach beides liefern, das Frontend entscheidet.

            # Kollegen finden
            colleagues = []
            for uid, other_details in all_assignments_in_shift.items():
                if uid != current_user_id and \
                        other_details.get('project') == proj and \
                        other_details.get('task') == task:
                    colleague_user = user_map.get(uid)
                    # Schön formatierter Name (Initial + Nachname) oder nur Nachname
                    name = colleague_user.get('nachname', uid).capitalize()
                    colleagues.append(name)

            return {
                "project": proj,
                "engine": eng,
                "task": task,
                "team": colleagues
            }

        mission_am = get_mission_details(assignments_today.get('AM', {}).get(current_user_id),
                                         assignments_today.get('AM', {}))
        if mission_am:
            mission_am['shift'] = 'Vormittag'
            my_missions.append(mission_am)

        mission_pm = get_mission_details(assignments_today.get('PM', {}).get(current_user_id),
                                         assignments_today.get('PM', {}))
        if mission_pm:
            # Check ob identisch zu AM (dann zusammenfassen oder nur einmal zeigen)
            # Wir zeigen es separat, wenn es unterschiedlich ist, sonst merken wir es an
            is_same = mission_am and mission_am['project'] == mission_pm['project'] and mission_am['task'] == \
                      mission_pm['task']

            if is_same:
                mission_am['shift'] = 'Ganztägig'  # Update AM Label
            else:
                mission_pm['shift'] = 'Nachmittag'
                my_missions.append(mission_pm)

    # 7. Testzellen Status (für Kachel)
    all_test_cells_with_data = load_locations_and_events()
    test_cell_statuses = [{'name': cell.get('ID', 'Unbekannt')} for cell in all_test_cells_with_data]
    test_cell_statuses.sort(key=lambda x: x['name'])

    sfm_texts = load_sfm_texts()

    # 8. Trainings laden
    upcoming_trainings = []
    if os.path.exists(TRAINING_DATA_FILE):
        try:
            with open(TRAINING_DATA_FILE, 'r', encoding='utf-8') as f:
                t_data = json.load(f)
                sessions = t_data.get('sessions', [])

                # Catalog Map robuster machen: Keys als String speichern
                catalog = {str(c['id']): c for c in t_data.get('catalog', [])}

                today_str = date.today().isoformat()

                # Filtern
                future_sessions = [s for s in sessions if s.get('date') >= today_str]
                future_sessions.sort(key=lambda x: x['date'])

                for sess in future_sessions[:3]:
                    # Lookup mit String-Konvertierung
                    cat_id = str(sess.get('catalogId'))
                    cat_item = catalog.get(cat_id)

                    if cat_item:
                        try:
                            date_obj = datetime.strptime(sess['date'], '%Y-%m-%d')
                            upcoming_trainings.append({
                                "title": cat_item['title'],
                                "category": cat_item.get('category', 'Training'),
                                "date_day": date_obj.day,
                                "date_month": date_obj.strftime('%b').upper(),
                                "time": sess.get('time', ''),
                                "image": cat_item.get('image'),
                                "location": sess.get('location', '')
                            })
                        except:
                            pass
        except Exception as e:
            print(f"Fehler beim Laden der Trainings für Dashboard: {e}")
    # --- NEU: ENTSCHEIDUNGEN (Triage & Review) ---
    my_decisions = []

    # Wir brauchen die Orders. Da wir sie hier nicht geladen haben, laden wir sie.
    m_data = load_maintenance_data()
    orders = m_data.get('orders', [])

    user_roles = session.get('roles', [])

    issues_with_actions = set()
    for o in orders:
        if o.get('type') == 'A' and o.get('linkedIssue'):
            issues_with_actions.add(o.get('linkedIssue'))

    for order in orders:
        if order.get('status') == 'Completed': continue

        # Fall 1: Triage
        if order.get('type') == 'I' and order.get('priority') == 'OPEN':
            if can_user_review(order, current_user_id, user_roles):
                my_decisions.append({
                    "type": "triage",
                    "title": order.get('task'),
                    "id": order.get('id'),
                    "date": order.get('date', '')
                })

        # Fall 2: Review (Review Ready)
        if order.get('status') == 'Review Ready':
            if can_user_review(order, current_user_id, user_roles):
                my_decisions.append({
                    "type": "review",
                    "title": order.get('task'),
                    "id": order.get('id'),
                    "date": order.get('date', '')
                })

        # Fall 3: Planung (Issue ohne Action)
        # Bedingung: Issue, nicht Completed, nicht OPEN (also schon bewertet), keine Actions
        if order.get('type') == 'I' and \
                order.get('status') != 'Completed' and \
                order.get('priority') != 'OPEN' and \
                order.get('id') not in issues_with_actions:

            # Berechtigung: Wer darf planen? (Analog zu Review)
            if can_user_review(order, current_user_id, user_roles):
                my_decisions.append({
                    "type": "planning",
                    "title": order.get('task'),
                    "id": order.get('id'),
                    "date": order.get('date', '')
                })

        # Fall 4: Vorgeschlagene Maßnahme (Proposed Action — wartet auf Freigabe)
        if order.get('type') == 'A' and order.get('status') == 'Proposed':
            if can_user_review(order, current_user_id, user_roles):
                my_decisions.append({
                    "type": "approval",
                    "title": order.get('task'),
                    "id": order.get('id'),
                    "date": order.get('date', '')
                })

    my_decisions.sort(key=lambda d: d.get('date', ''), reverse=True)

    # 9. Bestellsystem "Notwendig" für CONSUMABLEMANAGER / ADMIN
    pending_consumable_orders = []
    if Roles.CONSUMABLEMANAGER in user_roles or Roles.ADMIN in user_roles:
        try:
            consumable_orders, _ = order_system_load_data()
            pending_consumable_orders = [o for o in consumable_orders if o.get('status') == 'requested']
            pending_consumable_orders.sort(key=lambda x: x.get('isCritical', False), reverse=True)
        except Exception as e:
            print(f"Fehler beim Laden der Bestelldaten für Dashboard: {e}")

    return render_template_string(
        DASHBOARD_HTML_CONTENT,
        timelines=timelines,
        current_user_display_name=get_current_username_from_session(),
        current_user_initials=user_initials,
        test_cell_statuses=test_cell_statuses,
        present_employees_count=present_employees_count,
        free_employees_count=len(free_employees),
        free_employees=free_employees,
        busy_employees=busy_employees_details,
        sfm_texts=sfm_texts,
        my_missions=my_missions,
        my_decisions=my_decisions,
        pending_consumable_orders=pending_consumable_orders,
        active_page='dashboard',
        upcoming_trainings=upcoming_trainings,
        session=session,
        url_for=url_for
    )


@app.route('/admin/delete_db_file/<path:filepath>', methods=['POST'])
@admin_required
def delete_db_file(filepath):
    """Löscht eine Datei aus dem 'db' Ordner."""
    base_dir = os.path.abspath(DB_ROOT_PATH)
    full_path = os.path.abspath(os.path.join(base_dir, filepath))

    if not full_path.startswith(base_dir):
        flash('Ungültiger Dateipfad, Löschen verweigert.', 'error')
        return redirect(url_for('file_manager_page'))

    try:
        if os.path.isfile(full_path):
            os.remove(full_path)
            flash(f"Datei '{os.path.basename(filepath)}' wurde erfolgreich gelöscht.", 'success')
        else:
            flash('Datei nicht gefunden.', 'error')
    except Exception as e:
        flash(f"Fehler beim Löschen der Datei: {e}", 'error')

    return redirect(url_for('file_manager_page'))


@app.route('/admin/upload_new_db_file/', defaults={'dirpath': '.'}, methods=['POST'])
@app.route('/admin/upload_new_db_file/<path:dirpath>', methods=['POST'])
@admin_required
def upload_new_db_file(dirpath):
    """Lädt eine neue Datei in ein bestimmtes Verzeichnis im 'db' Ordner hoch."""
    base_dir = os.path.abspath(DB_ROOT_PATH)

    # Der Punkt '.' repräsentiert das Root-Verzeichnis 'db'
    if dirpath == '.':
        target_dir = base_dir
    else:
        target_dir = os.path.abspath(os.path.join(base_dir, dirpath))

    if not target_dir.startswith(base_dir) or not os.path.isdir(target_dir):
        flash('Ungültiges Zielverzeichnis.', 'error')
        return redirect(url_for('file_manager_page'))

    if 'new_file' not in request.files:
        flash('Keine Datei im Upload gefunden.', 'error')
        return redirect(url_for('file_manager_page'))

    file = request.files['new_file']
    if file.filename == '':
        flash('Keine Datei ausgewählt.', 'error')
        return redirect(url_for('file_manager_page'))

    if file:
        filename = secure_filename(file.filename)
        destination = os.path.join(target_dir, filename)

        if os.path.exists(destination):
            flash(
                f"Fehler: Eine Datei mit dem Namen '{filename}' existiert bereits. Bitte verwenden Sie die 'Ersetzen'-Funktion.",
                'error')
            return redirect(url_for('file_manager_page'))

        try:
            file.save(destination)
            flash(f"Datei '{filename}' wurde erfolgreich hochgeladen.", 'success')
        except Exception as e:
            flash(f"Fehler beim Speichern der neuen Datei: {e}", 'error')

    return redirect(url_for('file_manager_page'))


@app.route('/admin/process_templates')
@login_required
def serve_process_templates_management_page():
    user_roles = session.get('roles', [])
    allowed_roles = [Roles.ADMIN, Roles.PLANNER, Roles.TESTENGINEER]

    if not any(role in user_roles for role in allowed_roles):
        return redirect(url_for('dashboard_page'))  # Oder abort(403)

    config = load_app_config()
    timelines = config.get('timelines', [])

    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    return render_template_string(
        PROCESS_TEMPLATES_MANAGEMENT_HTML,
        timelines=timelines,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        active_page='process_templates',
        url_for=url_for,
        session=session
    )


@app.route('/api/process_templates', methods=['GET'])
@login_required
def api_get_process_templates():
    return jsonify(load_process_templates())


@app.route('/api/config')
@admin_required
def get_app_config():
    """Gibt die gesamte config.json zurück."""
    return jsonify(load_app_config())


@app.route('/api/process_templates', methods=['POST'])
@login_required  # War @admin_required
def api_save_process_templates():
    user_roles = session.get('roles', [])
    allowed_roles = [Roles.ADMIN, Roles.PLANNER, Roles.TESTENGINEER]

    if not any(role in user_roles for role in allowed_roles):
        return jsonify({"error": "Keine Berechtigung."}), 403

    full_updated_data = request.json

    # Wir prüfen nur noch, ob überhaupt Daten gesendet wurden
    if not isinstance(full_updated_data, dict):
        return jsonify({"error": "Ungültiges Datenformat. Ein JSON-Objekt wurde erwartet."}), 400

    try:
        # Wir müssen nichts mehr zusammenfügen. Wir überschreiben die Datei einfach
        # mit den neuen, vollständigen Daten.
        with open(PROCESS_TEMPLATES_FILE_PATH, 'w', encoding='utf-8') as f:
            json.dump(full_updated_data, f, indent=2, ensure_ascii=False)

        return jsonify({"message": "Prozess-Templates erfolgreich gespeichert."}), 200

    except IOError as e:
        print(f"FEHLER beim Speichern von process_templates.json: {e}")
        return jsonify({"error": "Fehler beim Schreiben der Datei."}), 500

@app.route('/api/manual_catalog', methods=['GET'])
@login_required
def api_get_manual_catalog():
    try:
        with open(MANUAL_CATALOG_FILE_PATH, 'r', encoding='utf-8') as f:
            return jsonify(json.load(f))
    except (IOError, json.JSONDecodeError):
        return jsonify([])


@app.route('/api/manual_catalog', methods=['POST'])
@admin_required
def api_save_manual_catalog():
    data = request.json
    if not isinstance(data, list):
        return jsonify({"error": "Erwartet eine JSON-Liste."}), 400
    try:
        with _get_file_lock(MANUAL_CATALOG_FILE_PATH):
            with open(MANUAL_CATALOG_FILE_PATH, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        return jsonify({"message": "Manual-Katalog gespeichert."}), 200
    except IOError as e:
        return jsonify({"error": str(e)}), 500


@app.route('/api/project/<string:project_uuid>/manuals', methods=['GET'])
@login_required
def api_get_project_manuals(project_uuid):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    path = os.path.join(PROJECT_MANUALS_DIR, f'{safe_uuid}.json')
    if not os.path.exists(path):
        return jsonify({"manuals": []})
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return jsonify(json.load(f))
    except (IOError, json.JSONDecodeError):
        return jsonify({"manuals": []})


@app.route('/api/project/<string:project_uuid>/manuals', methods=['POST'])
@login_required
def api_save_project_manuals(project_uuid):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    data = request.json
    if not isinstance(data, dict) or 'manuals' not in data:
        return jsonify({"error": "Ungültiges Format."}), 400
    from datetime import datetime as _dt
    payload = {
        "project_uuid": safe_uuid,
        "selected_at": _dt.now().isoformat(timespec='seconds'),
        "selected_by": get_current_username_from_session(),
        "manuals": data['manuals']
    }
    path = os.path.join(PROJECT_MANUALS_DIR, f'{safe_uuid}.json')
    try:
        with _get_file_lock(path):
            with open(path, 'w', encoding='utf-8') as f:
                json.dump(payload, f, indent=2, ensure_ascii=False)
        return jsonify({"message": "OK"}), 200
    except IOError as e:
        return jsonify({"error": str(e)}), 500


@app.route('/api/template_images/<path:filename>')
@login_required
def serve_template_image(filename):
    """Serves a template reference image from db/process/template_images/."""
    return send_from_directory(os.path.abspath(PROCESS_TEMPLATE_IMAGES_DIR), filename)


@app.route('/api/template_images', methods=['GET'])
@login_required
def list_template_images():
    """Returns a list of all uploaded template images."""
    os.makedirs(PROCESS_TEMPLATE_IMAGES_DIR, exist_ok=True)
    allowed_ext = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
    files = sorted([
        f for f in os.listdir(PROCESS_TEMPLATE_IMAGES_DIR)
        if os.path.isfile(os.path.join(PROCESS_TEMPLATE_IMAGES_DIR, f))
        and os.path.splitext(f)[1].lower() in allowed_ext
    ])
    return jsonify(files)


@app.route('/api/template_images/upload', methods=['POST'])
@login_required
def upload_template_image():
    """Uploads a new template image to db/process/template_images/."""
    user_roles = session.get('roles', [])
    if not any(r in user_roles for r in [Roles.ADMIN, Roles.PLANNER, Roles.TESTENGINEER]):
        return jsonify({"error": "Keine Berechtigung."}), 403

    if 'file' not in request.files:
        return jsonify({"error": "Keine Datei gesendet."}), 400
    file = request.files['file']
    if not file.filename:
        return jsonify({"error": "Kein Dateiname."}), 400

    allowed_ext = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
    ext = os.path.splitext(file.filename)[1].lower()
    if ext not in allowed_ext:
        return jsonify({"error": f"Dateityp '{ext}' nicht erlaubt."}), 400

    filename = secure_filename(file.filename)
    os.makedirs(PROCESS_TEMPLATE_IMAGES_DIR, exist_ok=True)
    save_path = os.path.join(PROCESS_TEMPLATE_IMAGES_DIR, filename)
    file.save(save_path)
    return jsonify({"filename": filename, "url": f"/api/template_images/{filename}"}), 201


@app.route('/api/template_images/<path:filename>', methods=['DELETE'])
@login_required
def delete_template_image(filename):
    """Deletes a template image from db/process/template_images/."""
    user_roles = session.get('roles', [])
    if not any(r in user_roles for r in [Roles.ADMIN, Roles.PLANNER, Roles.TESTENGINEER]):
        return jsonify({"error": "Keine Berechtigung."}), 403
    safe_name = secure_filename(filename)
    file_path = os.path.join(PROCESS_TEMPLATE_IMAGES_DIR, safe_name)
    if not os.path.isfile(file_path):
        return jsonify({"error": "Datei nicht gefunden."}), 404
    os.remove(file_path)
    return jsonify({"success": True}), 200


@app.route('/api/available_documents')
@login_required
def api_get_available_documents():
    if not os.path.isdir(DOCUMENTS_FOLDER_PATH):
        # Erstelle den Ordner, wenn er nicht existiert, um Fehler zu vermeiden
        os.makedirs(DOCUMENTS_FOLDER_PATH, exist_ok=True)
        return jsonify([])
    try:
        # Liste nur Dateien auf, keine Ordner, und filtere versteckte Dateien (z.B. .DS_Store auf Mac)
        files = [f for f in os.listdir(DOCUMENTS_FOLDER_PATH)
                 if os.path.isfile(os.path.join(DOCUMENTS_FOLDER_PATH, f)) and not f.startswith('.')]
        return jsonify(sorted(files))
    except Exception as e:
        print(f"FEHLER beim Lesen des Dokumentenordners: {e}")
        return jsonify({"error": "Fehler beim Zugriff auf den Dokumentenordner."}), 500


@app.route('/admin/file_manager')
@admin_required
def file_manager_page():
    if not os.path.exists(DB_ROOT_PATH):
        flash(f"Fehler: Das Verzeichnis '{DB_ROOT_PATH}' existiert nicht!", 'error')
        tree = []
    else:
        tree = build_file_tree(DB_ROOT_PATH)

    # NEU: Initialen und Benutzername ermitteln
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    return render_template_string(
        FILE_MANAGER_HTML_CONTENT,
        file_tree=tree,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        active_page='file_manager',  # Eindeutiger Name für diese Seite
        url_for=url_for,
        get_flashed_messages=get_flashed_messages,  # Bleibt wichtig für diese Seite
        session=session
    )


@app.route('/admin/download_db_file/<path:filepath>')
@admin_required
def download_db_file(filepath):
    """Stellt eine Datei aus dem 'db' Ordner zum Download bereit."""
    base_dir = os.path.abspath(DB_ROOT_PATH)
    full_path = os.path.abspath(os.path.join(base_dir, filepath))

    if not full_path.startswith(base_dir):
        abort(403)  # Forbidden

    if not os.path.isfile(full_path):
        abort(404)  # Not Found

    return send_file(full_path, as_attachment=True)


@app.route('/admin/upload_db_file/<path:filepath>', methods=['POST'])
@admin_required
def upload_db_file(filepath):
    """Ersetzt eine bestehende Datei im 'db' Ordner."""
    base_dir = os.path.abspath(DB_ROOT_PATH)
    full_path = os.path.abspath(os.path.join(base_dir, filepath))

    if not full_path.startswith(base_dir):
        flash('Ungültiger Dateipfad.', 'error')
        return redirect(url_for('file_manager_page'))

    if 'file' not in request.files:
        flash('Keine Datei im Upload gefunden.', 'error')
        return redirect(url_for('file_manager_page'))

    file = request.files['file']
    if file.filename == '':
        flash('Keine Datei ausgewählt.', 'error')
        return redirect(url_for('file_manager_page'))

    if file:
        try:
            file.save(full_path)
            flash(
                f"Datei '{os.path.basename(filepath)}' wurde erfolgreich ersetzt. Die Anwendung muss möglicherweise neu gestartet werden, damit die Änderungen wirksam werden.",
                'success')
        except Exception as e:
            flash(f"Fehler beim Speichern der Datei: {e}", 'error')

    return redirect(url_for('file_manager_page'))


@app.route('/download_document/<path:project_id>/<step_name>')
@login_required
def download_document_route(project_id, step_name):
    if '_' not in project_id:
        return "Ungültige Projekt-ID-Formatierung.", 400
    try:
        project_code, engine_serial = project_id.split('_', 1)
    except ValueError:
        return f"Ungültige Projekt-ID: '{project_id}'", 400

    try:
        # 1. Lade die Konfiguration, um alle möglichen Projektdateien zu kennen
        config = load_app_config()
        all_possible_data_files = set()
        for tl in config.get('timelines', []):
            if tl.get('data_file'):
                all_possible_data_files.add(tl.get('data_file'))
            if tl.get('data_files'):
                all_possible_data_files.update(tl.get('data_files'))

        # 2. Lade alle Projekte aus allen konfigurierten Dateien
        all_known_projects = load_timeline_projects_for_flask(list(all_possible_data_files))

        # 3. Suche das spezifische Projekt in der frisch geladenen Gesamtliste
        project_data = next((p for p in all_known_projects if
                             p.get('project_code') == project_code and p.get('engine_serial') == engine_serial), None)

        if not project_data:
            return f"Projekt mit ID {project_id} nicht gefunden.", 404

        # 4. Der Rest der Logik bleibt identisch
        templates = load_process_templates()
        engine_type = project_data.get('engine_type')
        template = find_best_template_for_engine(engine_type, templates)

        step_config = next((s for s in template.get('steps', []) if s['name'] == step_name), None)

        if not step_config or not step_config.get('document_template'):
            return f"Kein Dokument-Template für Schritt '{step_name}' beim Typ '{engine_type}' definiert.", 404

        template_name = step_config['document_template']
        template_path = os.path.join(DOCUMENTS_FOLDER_PATH, template_name)
        if not os.path.exists(template_path):
            return f"Template-Datei '{template_name}' nicht gefunden.", 404

        context = {
            'WORK_ORDER_PLACEHOLDER': project_data.get('project_code', ''),
            'ENGINE_SERIAL_PLACEHOLDER': project_data.get('engine_serial', '')
        }

        document = Document(template_path)
        docx_find_replace(document, context)

        file_stream = io.BytesIO()
        document.save(file_stream)
        file_stream.seek(0)

        safe_project_code = project_data.get('project_code', '').replace('.', '')
        download_filename = f"{step_name}_{safe_project_code}_{project_data.get('engine_serial', '')}.docx"

        return send_file(
            file_stream,
            as_attachment=True,
            download_name=download_filename,
            mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        )
    except Exception as e:
        print(f"!!! KRITISCHER FEHLER bei der Dokumentenerstellung für {project_id}:")
        traceback.print_exc()
        return "Fehler bei der Dokumentenerstellung. Bitte Konsole prüfen.", 500


@app.route('/api/manual_sap_update', methods=['POST'])
@admin_required
def api_manual_sap_update():
    if 'sap_file' not in request.files:
        return jsonify({"error": "Keine Datei im Request gefunden."}), 400

    file = request.files['sap_file']

    if file.filename == '':
        return jsonify({"error": "Keine Datei ausgewählt."}), 400

    if not file.filename.lower().endswith('.csv'):
        return jsonify({"error": "Ungültiger Dateityp. Bitte eine CSV-Datei hochladen."}), 400

    try:
        file_stream = io.BytesIO(file.read())

        global ALL_PROJECTS
        current_project_order = []
        for proj in ALL_PROJECTS:
            key = (proj.get('project_code', ''), proj.get('engine_serial', ''))
            if key[0] and key[1]:
                current_project_order.append(key)

        # `filter_sap_data` kann jetzt direkt den Stream verarbeiten!
        filtered_df = filter_sap_data(file_stream)

        if filtered_df is None or filtered_df.empty:
            return jsonify(
                {"message": "Keine neuen oder relevanten Daten in der hochgeladenen SAP-Datei gefunden."}), 200

        changes_were_made = merge_into_timeline_projects(CURRENT_PROJECT_FILE_PATH, filtered_df, current_project_order)

        # Wichtig: Die ALL_PROJECTS im Speicher neu laden, da die CSV-Datei überschrieben wurde
        load_timeline_projects_for_flask(CURRENT_PROJECT_FILE_PATH)

        if changes_were_made:
            return jsonify({"message": "Timeline erfolgreich mit manuell hochgeladenen SAP-Daten aktualisiert."}), 200
        else:
            return jsonify({
                "message": "Projekte sind bereits auf dem neuesten Stand (keine Änderungen durch manuelle SAP-Daten)."}), 200

    except Exception as e:
        traceback.print_exc()
        return jsonify({"error": f"Ein Fehler ist beim manuellen SAP-Update aufgetreten: {str(e)}"}), 500


@app.route('/api/upload_employee_presence', methods=['POST'])
@admin_required  # Wichtig: Nur Admins dürfen diese Aktion ausführen!
def api_upload_employee_presence():
    if 'presence_file' not in request.files:
        return jsonify({"error": "Keine Datei im Request gefunden."}), 400

    file = request.files['presence_file']

    if file.filename == '':
        return jsonify({"error": "Keine Datei ausgewählt."}), 400

    if not file.filename.lower().endswith('.csv'):
        return jsonify({"error": "Ungültiger Dateityp. Bitte eine CSV-Datei hochladen."}), 400

    try:
        uploaded_content = file.read().decode('utf-8-sig')
        with open(EMPLOYEES_FILE_PATH, 'w', newline='', encoding='utf-8-sig') as target_file:
            target_file.write(uploaded_content)

        global ALL_EMPLOYEES, ALL_QUALIFICATIONS
        parsed = load_employees_from_csv(EMPLOYEES_FILE_PATH, MERKMALE_IN_ORDER)
        merge_csv_employees_into_attendance_json(parsed)
        ALL_QUALIFICATIONS = []
        load_and_prepare_qualifications_data()

        return jsonify({
            "message": f"Anwesenheitsdaten aus '{file.filename}' importiert. Nur bekannte Mitarbeiter aktualisiert, Wochen aus der CSV überschrieben, Stammdaten unverändert."}), 200

    except Exception as e:
        print(f"FEHLER beim Verarbeiten der hochgeladenen Anwesenheitsdatei: {e}")
        traceback.print_exc()
        return jsonify({"error": "Ein interner Fehler ist beim Speichern der Datei aufgetreten."}), 500


@app.route('/api/create_manual_project/<string:timeline_id>', methods=['POST'])
@admin_required
def api_create_manual_project(timeline_id):
    # 1. Konfiguration laden
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if not timeline_config or 'data_file' not in timeline_config:
        return jsonify({"error": "Timeline-Konfiguration zum Projekt erstellen nicht gefunden."}), 404

    target_file = timeline_config['data_file']

    # 2. Daten validieren
    data = request.json
    required_fields = ['name', 'engine_type', 'project_code', 'engine_serial', 'events_start_date']
    if not data or not all(field in data for field in required_fields):
        return jsonify({"error": "Fehlende Projektdaten."}), 400

    # 3. Projekt-Existenz prüfen
    current_projects_for_target_file = load_timeline_projects_for_flask([target_file])
    project_key = (data['project_code'], data['engine_serial'])
    if any(p.get('project_code') == project_key[0] and p.get('engine_serial') == project_key[1] for p in
           current_projects_for_target_file):
        return jsonify({
            "error": "Ein Projekt mit diesem Projekt-Code und Engine Serial existiert bereits in dieser Timeline."}), 409

    # 4. Startdatum verarbeiten
    try:
        start_date = datetime.strptime(data['events_start_date'], '%Y-%m-%d').date()
        ts_datetime = datetime.combine(start_date, time(8, 0))  # Annahme: Start um 8 Uhr
    except ValueError:
        return jsonify({"error": "Ungültiges Datumsformat. Bitte YYYY-MM-DD verwenden."}), 400

    # 5. Events dynamisch aus Template generieren
    engine_type = data['engine_type']
    process_templates = load_process_templates()
    template = find_best_template_for_engine(engine_type, process_templates)
    template_events = template.get("timeline_events", [])

    dynamic_events = []
    current_date = ts_datetime.date()
    current_shift = 'AM'

    for event_def in template_events:
        duration = event_def.get("duration_shifts", 1)
        start_date_loop = current_date
        start_shift_loop = current_shift

        end_date_loop = start_date_loop
        end_shift_loop = start_shift_loop

        # KORREKTUR: Schleife läuft exakt 'duration' mal (keine -1 mehr)
        for _ in range(duration):
            if end_shift_loop == 'AM':
                end_shift_loop = 'PM'
            else:
                end_shift_loop = 'AM'
                end_date_loop += timedelta(days=1)

        dynamic_events.append({
            'title': event_def.get("name", "Unknown"),
            'start': start_date_loop.strftime("%Y-%m-%d"),
            'start_shift': start_shift_loop,
            'end': end_date_loop.strftime("%Y-%m-%d"),
            'end_shift': end_shift_loop,
            'color': event_def.get("color", "grey")
        })

        # KORREKTUR: Der Start des nächsten Events ist exakt das Ende des aktuellen
        # (da die Schleife oben bereits einen Schritt weiter gegangen ist)
        current_date = end_date_loop
        current_shift = end_shift_loop

    # 6. Neues Projekt-Objekt erstellen
    new_project = {
        'name': data['name'],
        'engine_type': data['engine_type'],
        'project_code': data['project_code'],
        'engine_serial': data['engine_serial'],
        'customer': data.get('customer', '').strip() or 'Manuell erstellt',
        'BE': '',
        'TS': ts_datetime.strftime("%d.%m.%Y %H:%M:%S"),
        'TE': '',
        'SA': '',
        'active': True,
        'events': dynamic_events
    }

    # 7. Speichern
    current_projects_for_target_file.insert(0, new_project)

    if save_projects_for_flask(target_file, current_projects_for_target_file):
        return jsonify({"message": f"Projekt '{new_project['name']}' wurde erfolgreich erstellt und gespeichert."}), 201
    else:
        return jsonify({"error": "Projekt konnte erstellt, aber nicht in der CSV-Datei gespeichert werden."}), 500

@app.route('/api/assignment_rules/<string:timeline_id>', methods=['GET'])
@admin_required
def get_assignment_rules(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)

    if not timeline_config or 'assignment_rules_file' not in timeline_config:
        return jsonify({"error": f"Keine 'assignment_rules_file' für Timeline '{timeline_id}' konfiguriert."}), 404

    rules_file_path = timeline_config['assignment_rules_file']
    rules = load_assignment_rules(rules_file_path)

    # Die Optionen (mögliche Skills etc.) können global bleiben, da sie aus allen Qualifikationen stammen
    all_merkmale_for_skills = {m.get('merkmal', '').strip() for q in ALL_QUALIFICATIONS for m in q.get('merkmale', [])
                               if m.get('merkmal', '').strip()}
    all_event_titles = sorted(list(set(evt.get('title', '').upper() for proj in ALL_PROJECTS for evt in
                                       proj.get('events', [])))) if ALL_PROJECTS else []
    all_engine_types = sorted(list(set(merkmal.split('/')[0] for merkmal in all_merkmale_for_skills if '/' in merkmal)))

    return jsonify({
        "rules": rules,
        "options": {
            "event_titles": all_event_titles,
            "engine_types": all_engine_types,
            "skills": sorted(list(all_merkmale_for_skills))
        }
    })


@app.route('/api/process_template_options')
@login_required
def api_get_process_template_options():
    """
    Gibt eine JSON-Struktur zurück, die Triebwerkstypen auf die Namen ihrer
    Timeline-Events abbildet. Nützlich für dynamische Dropdowns im Frontend.
    Beispiel: {"PW1900G": ["Rigging", "Test"], "default": ["AUF", "Test", "AB"]}
    """
    try:
        templates = load_process_templates()
        options = {}
        for engine_type, config in templates.items():
            # Stelle sicher, dass 'timeline_events' existiert und eine Liste ist
            timeline_events = config.get("timeline_events", [])
            if isinstance(timeline_events, list):
                # Extrahiere nur die Namen der Events
                event_names = [event.get("name") for event in timeline_events if event.get("name")]
                options[engine_type] = event_names

        return jsonify(options)
    except Exception as e:
        print(f"FEHLER in api_get_process_template_options: {e}")
        return jsonify({"error": "Fehler beim Laden der Template-Optionen."}), 500

@app.route('/api/assignment_rules/<string:timeline_id>', methods=['POST'])
@admin_required
def update_assignment_rules(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)

    if not timeline_config or 'assignment_rules_file' not in timeline_config:
        return jsonify({"error": f"Keine 'assignment_rules_file' für Timeline '{timeline_id}' konfiguriert."}), 404

    data = request.json
    if not data or 'rules' not in data:
        return jsonify({"error": "Ungültige Daten"}), 400

    new_rules = data['rules']
    import uuid
    for rule in new_rules:
        if not rule.get('rule_id'):
            rule['rule_id'] = f"rule_{uuid.uuid4().hex[:8]}"

    rules_file_path = timeline_config['assignment_rules_file']
    if save_assignment_rules(new_rules, rules_file_path):
        return jsonify({"message": "Zuordnungsregeln erfolgreich gespeichert."}), 200
    else:
        return jsonify({"error": "Fehler beim Speichern der Regeln."}), 500


@app.route('/employee_dashboard')
@login_required
@admin_required
def serve_employee_dashboard():
    config = load_app_config()
    timelines = config.get('timelines', [])
    dashboard_data = get_employee_dashboard_data()
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    m_data = load_maintenance_data()
    orders = m_data.get('orders', [])

    sfm_details = {
        "critical_prio": [],  # Nur Prio Critical
        "blocked_units": [],  # Nur Unit Blocker
        "blocked_hw": [],  # Nur Hardware Blocker
        "other_open": []
    }

    for o in orders:
        if o.get('type') == 'I' and o.get('status') != 'Completed':
            display_text = f"{o.get('id')}: {o.get('task')}"

            is_crit = o.get('priority') == 'Critical'
            is_unit = o.get('isUnitBlocker') in [True, "true"]
            is_hw = len(o.get('blockedAssetIds', [])) > 0

            if is_crit: sfm_details["critical_prio"].append(display_text)
            if is_unit: sfm_details["blocked_units"].append(display_text)
            if is_hw: sfm_details["blocked_hw"].append(display_text)

            if not is_crit and not is_unit and not is_hw:
                sfm_details["other_open"].append(display_text)

    # Statistiken für die Kacheln berechnen
    sfm_stats = {
        # Safety Kachel: Alles was Critical ODER Unit/HW Blocker ist (Eindeutige IDs)
        "critical_total": len(
            set(sfm_details["critical_prio"] + sfm_details["blocked_units"] + sfm_details["blocked_hw"])),
        # Quality Kachel: Alle offenen Issues
        "open_issues": len(orders) - len([o for o in orders if o.get('status') == 'Completed' or o.get('type') != 'I']),
        "details": sfm_details
    }

    sfm_texts = load_sfm_texts()
    sfm_calendar_data = {}
    if os.path.exists(SFM_CALENDAR_FILE):
        try:
            with open(SFM_CALENDAR_FILE, 'r', encoding='utf-8') as f:
                sfm_calendar_data = json.load(f)
        except: sfm_calendar_data = {}

    backlog_employees = sorted([e.get('name', '') for e in ALL_EMPLOYEES if e.get('name')])

    return render_template_string(
        EMPLOYEE_DASHBOARD_HTML_CONTENT,
        data=dashboard_data,
        current_user_display_name=user_name,
        timelines=timelines,
        current_user_initials=user_initials,
        active_page='employee_dashboard',
        url_for=url_for,
        session=session,
        sfm_stats=sfm_stats,
        sfm_calendar_data=sfm_calendar_data,
        sfm_texts=load_sfm_texts(),  # Falls noch für MOTD gebraucht
        sfm_people_groups_filter=sfm_texts.get('people_groups_filter', []),
        sfm_efficiency_data=sfm_texts.get('efficiency_data', {}),
        sfm_efficiency_target=sfm_texts.get('efficiency_target', 85),
        backlog_employees=backlog_employees
    )

@app.route('/api/sfm/efficiency', methods=['GET'])
@login_required
def api_get_sfm_efficiency():
    """Gibt alle gespeicherten Effizienzdaten + Zielwert zurück."""
    sfm_texts = load_sfm_texts()
    return jsonify({
        "efficiency_data": sfm_texts.get('efficiency_data', {}),
        "efficiency_target": sfm_texts.get('efficiency_target', 85)
    })


@app.route('/api/sfm/efficiency/day', methods=['POST'])
@login_required
def api_save_sfm_efficiency_day():
    """Speichert die Effizienzwerte für einen einzelnen Tag."""
    data = request.json
    if not data or 'date' not in data:
        return jsonify({"error": "Datum fehlt"}), 400
    date_key = data['date']  # YYYY-MM-DD
    sfm_texts = load_sfm_texts()
    if 'efficiency_data' not in sfm_texts:
        sfm_texts['efficiency_data'] = {}
    sfm_texts['efficiency_data'][date_key] = {
        'IGT':    data.get('IGT'),
        'CFX':    data.get('CFX'),
        'PWC':    data.get('PWC'),
        'Gesamt': data.get('Gesamt')
    }
    if save_sfm_texts_data(sfm_texts):
        return jsonify({"message": "Gespeichert."}), 200
    return jsonify({"error": "Fehler beim Speichern."}), 500


@app.route('/api/sfm/efficiency/target', methods=['POST'])
@login_required
def api_save_sfm_efficiency_target():
    """Speichert den Zielwert."""
    data = request.json
    if data is None or 'target' not in data:
        return jsonify({"error": "Zielwert fehlt"}), 400
    sfm_texts = load_sfm_texts()
    sfm_texts['efficiency_target'] = data['target']
    if save_sfm_texts_data(sfm_texts):
        return jsonify({"message": "Zielwert gespeichert."}), 200
    return jsonify({"error": "Fehler beim Speichern."}), 500


@app.route('/api/sfm/people_groups', methods=['GET'])
@login_required
def api_get_sfm_people_groups():
    """Gibt verfügbare Gruppen (aus Mitarbeiterdaten) + aktuell gespeicherte Auswahl zurück."""
    available = sorted(list({
        e.get('quali', '').strip() for e in ALL_EMPLOYEES if e.get('quali', '').strip()
    }))
    sfm_texts = load_sfm_texts()
    selected = sfm_texts.get('people_groups_filter', [])
    if not selected:
        selected = available  # Leere Liste = alle Gruppen aktiv
    return jsonify({"available": available, "selected": selected})


@app.route('/api/sfm/people_groups', methods=['POST'])
@admin_required
def api_save_sfm_people_groups():
    """Speichert die Gruppen-Auswahl für die People-Kachel in SFM_texts.json."""
    data = request.json
    if data is None or 'selected' not in data:
        return jsonify({"error": "Ungültige Daten"}), 400
    sfm_texts = load_sfm_texts()
    sfm_texts['people_groups_filter'] = data['selected']
    if save_sfm_texts_data(sfm_texts):
        return jsonify({"message": "Gruppen-Filter gespeichert."}), 200
    return jsonify({"error": "Fehler beim Speichern."}), 500


@app.route('/api/save_sfm_calendar', methods=['POST'])
@login_required
def api_save_sfm_calendar():
    data = request.json
    if save_json_file(SFM_CALENDAR_FILE, data):
        return jsonify({"status": "success"}), 200
    return jsonify({"error": "Speichern fehlgeschlagen"}), 500

@app.route('/api/v2/recalculate_assignments/<string:timeline_id>', methods=['POST'])
@login_required
def api_recalculate_assignments(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)

    if not timeline_config:
        return jsonify({"error": f"Keine Konfiguration für Timeline '{timeline_id}' gefunden."}), 404

    data = request.json
    if not data or 'projects' not in data:
        return jsonify({"error": "Ungültige Daten. Projektliste fehlt."}), 400

    projects_from_client = data['projects']
    assignment_groups = data.get('assignment_group', [])
    selected_test_cell_ids = data.get('selected_test_cells', [])

    rules_file_path = timeline_config.get('assignment_rules_file')
    assignment_rules = load_assignment_rules(rules_file_path) if rules_file_path else []

    for project in projects_from_client:
        project_id_prefix = f"{project.get('project_code')}_{project.get('engine_serial')}"
        prepared_events = []
        for i, event in enumerate(project.get('events', [])):
            if not isinstance(event, dict): continue
            if 'id' not in event or not event['id']:
                event['id'] = f"{project_id_prefix}_{event.get('title', 'event')}_{i}"
            start_dt = get_datetime_from_date_shift(parse_date_from_string(event.get('start')),
                                                    event.get('start_shift'))
            end_dt = get_datetime_from_date_shift(parse_date_from_string(event.get('end')), event.get('end_shift'))
            if start_dt and end_dt:
                event['start_datetime'] = start_dt.isoformat()
                event['end_datetime'] = end_dt.isoformat()
                prepared_events.append(event)
        project['events'] = prepared_events

    if not ALL_EMPLOYEES: load_and_prepare_qualifications_data()
    if not ALL_QUALIFICATIONS: load_and_prepare_qualifications_data()
    manual_assignments_file = timeline_config.get('manual_assignments_file')
    manual_assignments = load_assigned_employees(manual_assignments_file) if manual_assignments_file else []

    today = date.today()
    timeline_start_date = (today - timedelta(days=today.weekday())) - timedelta(weeks=3)
    num_days = 10 * 7

    process_templates = load_process_templates()
    manual_hw_assignments = load_hardware_assignments(timeline_id)
    available_kit_ids = timeline_config.get('available_hardware_kits', [])

    calculated_data = calculate_timeline_assignments(
        projects_from_client, ALL_EMPLOYEES, ALL_QUALIFICATIONS,
        manual_assignments, timeline_start_date, num_days,
        assignment_rules=assignment_rules,
        assignment_groups=assignment_groups,
        selected_test_cell_ids=selected_test_cell_ids,
        timeline_id=timeline_id,
        previous_global_assignments=global_scheduler.assignments,
        process_templates=process_templates,
        available_hardware_kits=available_kit_ids,
        manual_hw_assignments=manual_hw_assignments
    )

    # Baue template_event_map: engine_type → event_name → event_def
    # Wird im Frontend genutzt für Farben und assigns-Flags
    engine_types_in_projects = list({p.get('engine_type', '') for p in projects_from_client if p.get('engine_type')})
    template_event_map = {}
    for eng in engine_types_in_projects:
        tmpl = find_best_template_for_engine(eng, process_templates)
        if not tmpl:
            continue
        event_map = {}
        for ev_def in tmpl.get('timeline_events', []):
            name = ev_def.get('name', '').strip()
            if not name:
                continue
            assigns = ev_def.get('assigns', {})
            releases = ev_def.get('releases', {})
            event_map[name] = {
                'people': assigns.get('people', True),
                'rooms': assigns.get('rooms', name.upper() == 'TEST'),
                'hardware': assigns.get('hardware', False),
                'releases_hardware': releases.get('hardware', False),
                'color': ev_def.get('color', '#8B5CF6'),
            }
        if event_map:
            template_event_map[eng] = event_map

    # Baue available_hardware_kits Liste mit Kit-Details für das Frontend
    hw_data = load_test_equipment_data()
    boms = (hw_data or {}).get('boms', {})
    available_hardware_kits_detail = [
        {'id': kid, 'name': boms[kid].get('name', ''), 'code': boms[kid].get('code', '')}
        for kid in available_kit_ids if kid in boms
    ]

    # Reichere hardware_assignments mit Kit-Details an
    raw_hw = calculated_data.get('hardware_assignments', {})
    enriched_hw = {}
    for event_id, hw_entry in raw_hw.items():
        kid = hw_entry.get('kit_id', '')
        bom = boms.get(kid, {})
        enriched_hw[event_id] = {
            'kit_id': kid,
            'code': bom.get('code', ''),
            'name': bom.get('name', ''),
            'is_manual': hw_entry.get('is_manual', False),
            'status': bom.get('status', ''),
        }

    return jsonify({
        "calculated_data": calculated_data,
        "manual_assignments": manual_assignments,
        "template_event_map": template_event_map,
        "available_hardware_kits": available_hardware_kits_detail,
        "hardware_assignments": enriched_hw,
    })


@app.route('/api/timeline_data', methods=['GET'])
@login_required
def get_timeline_data_api():
    """
    Diese Route ist jetzt der dedizierte Endpunkt für die Read-Only-Ansicht.
    Sie ruft die zentrale API-Logik auf, aber schneidet die Daten auf den
    gewünschten Zeitraum (1 oder 2 Wochen) zu.
    """
    try:
        # 1. Parameter aus der Anfrage lesen
        weeks_to_display = request.args.get('weeks', default=2, type=int)
        if weeks_to_display not in [1, 2]:
            weeks_to_display = 2

        # 2. Rufe die neue, zentrale Datenquelle auf, um alle Daten zu erhalten
        unified_response = api_get_unified_timeline_data()
        unified_data = unified_response.get_json()

        if unified_response.status_code != 200:
            return jsonify(unified_data), unified_response.status_code

    except Exception as e:
        print(f"Schwerwiegender Fehler beim Aufruf von api_get_unified_timeline_data: {e}")
        traceback.print_exc()
        return jsonify({"error": "Interne Datenbeschaffung fehlgeschlagen."}), 500

    try:
        # 3. Zeitraum für die statische Ansicht definieren
        today = date.today()
        # Montag der aktuellen Woche ist der Startpunkt
        start_of_period = today - timedelta(days=today.weekday())
        num_display_days = 7 * weeks_to_display

        # Mitarbeiter-Map für schnellen Zugriff erstellen
        all_employees_data = unified_data.get('employees', [])
        employee_map_by_id = {emp.get('id', '').lower(): emp for emp in all_employees_data if emp.get('id')}

        # 4. Übersetze die Datenstruktur und FILTERE sie auf den gewünschten Zeitraum
        projects_for_readonly = []
        for proj in unified_data.get('projects', []):
            if not proj.get('active'):
                continue

            tasks_within_period = []
            events_in_project = proj.get('events', [])
            event_starts = {e['start_datetime'] for e in events_in_project if 'start_datetime' in e}
            event_ends = {e['end_datetime'] for e in events_in_project if 'end_datetime' in e}

            for event in events_in_project:
                if 'start_datetime' not in event or 'end_datetime' not in event:
                    continue

                try:
                    # ALT: start_dt = datetime.fromisoformat(event['start_datetime'])
                    # NEU: Wir parsen explizit ohne Zeitzone
                    start_dt = datetime.fromisoformat(event['start_datetime'].replace('Z', ''))
                    end_dt = datetime.fromisoformat(event['end_datetime'].replace('Z', ''))
                except (ValueError, TypeError):
                    continue

                is_connected_to_prev = event['start_datetime'] in event_ends
                is_connected_to_next = event['end_datetime'] in event_starts

                current_dt = start_dt
                slot_index = 0
                while current_dt < end_dt:
                    # Hier wird gefiltert!
                    day_offset = (current_dt.date() - start_of_period).days
                    if 0 <= day_offset < num_display_days:
                        # Die Spaltennummer wird jetzt relativ zum Start der Anzeigeperiode berechnet
                        start_column = (day_offset * 2) + (0 if current_dt.hour < 12 else 1) + 1

                        event_slot_key = f"{event.get('id', '')}_{slot_index}"
                        assignment_info = unified_data.get('calculated_data', {}).get('assignments', {}).get(
                            event_slot_key, {})
                        employee_ids = assignment_info.get("employees", [])
                        employee_initials = [employee_map_by_id.get(eid.lower(), {}).get('initials', '??') for eid in
                                             employee_ids if eid]

                        tasks_within_period.append({
                            "task_name": event.get('title'),
                            "start_datetime_iso": current_dt.isoformat(),
                            "people_initials": employee_initials,
                            "start_column_on_timeline": start_column,
                            "is_connected_to_prev": is_connected_to_prev and slot_index == 0,
                            "is_connected_to_next": is_connected_to_next and slot_index == int(
                                (end_dt - start_dt).total_seconds() / 43200) - 1,
                            "project_name": proj.get('name'),
                            "project_typ": "ENGINE",
                            "engine_serial": proj.get('engine_serial'),
                            "original_shift_name": 'AM' if current_dt.hour < 12 else 'PM'
                        })

                    current_dt += timedelta(hours=12)
                    slot_index += 1

            if tasks_within_period:
                projects_for_readonly.append({
                    "project_typ": "ENGINE",
                    "project_name": proj.get('name'),
                    "engine_type": proj.get('engine_type'),
                    "tasks": tasks_within_period
                })

        todays_tasks_summary = []
        today_iso = date.today().isoformat()
        for proj in projects_for_readonly:
            for task in proj.get('tasks', []):
                task_date = datetime.fromisoformat(task['start_datetime_iso']).date().isoformat()
                if task_date == today_iso:
                    todays_tasks_summary.append({
                        "task_name": task.get('task_name'),
                        "people_initials": task.get('people_initials', []),
                        "original_shift_name": task.get('original_shift_name'),
                        "project_name": proj.get('project_name'),
                        "project_typ": proj.get('project_typ', "ENGINE")
                    })
        todays_tasks_summary.sort(key=lambda x: (x['original_shift_name'] == 'PM', x['project_name']))

        return jsonify({
            "projects": projects_for_readonly,
            "todays_summary": todays_tasks_summary,
            "timeline_start_date": start_of_period.isoformat(),  # Wichtig: Das korrekte Startdatum senden!
            "num_days_to_display": num_display_days,
            "num_columns_to_display": num_display_days * 2
        })

    except Exception as e:
        print(f"FEHLER bei der Übersetzung der Daten für die Read-Only-Ansicht: {e}")
        traceback.print_exc()
        return jsonify({"error": "Fehler bei der Datenaufbereitung für die Anzeige."}), 500


@app.route('/api/v2/timeline_data/<string:timeline_id>')
@login_required
def api_get_unified_timeline_data(timeline_id):
    # NEU: Lese den view_mode aus der URL
    view_mode = request.args.get('view_mode', 'editable')  # Standard ist 'editable', wenn nichts übergeben wird

    print("\n--- DEBUG: api_get_unified_timeline_data AUFGERUFEN ---")
    print(f"--- DEBUG: Angefragte timeline_id: {timeline_id}, view_mode: {view_mode} ---")

    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if not timeline_config:
        return jsonify({"error": "Timeline-Konfiguration nicht gefunden."}), 404

    file_paths = []
    if 'data_files' in timeline_config:
        file_paths = timeline_config['data_files']
    elif 'data_file' in timeline_config:
        file_paths = [timeline_config['data_file']]

    if not file_paths:
        return jsonify(
            {"error": f"Keine 'data_file' oder 'data_files' für Timeline '{timeline_id}' konfiguriert."}), 500

    timeline_projects = load_timeline_projects_for_flask(file_paths)
    print(f"--- DEBUG: {len(timeline_projects)} Projekte initial aus CSV-Dateien geladen. ---")

    # --- ANGEPASSTE FILTERLOGIK ---

    # Filtere, wenn der Aufruf explizit als 'static' markiert ist
    if view_mode == 'static':
        print("--- DEBUG: view_mode ist 'static', starte Filterung. ---")
        today = date.today()
        start_of_current_week = today - timedelta(days=today.weekday())
        end_of_next_week = start_of_current_week + timedelta(days=13)

        print(
            f"--- DEBUG: Relevanter Zeitraum: {start_of_current_week.isoformat()} bis {end_of_next_week.isoformat()} ---")

        relevant_projects = []
        for project in timeline_projects:
            if not project.get('active'):
                continue

            has_relevant_event = False
            for event in project.get('events', []):
                try:
                    event_start_date = parse_date_from_string(event.get('start'))
                    event_end_date = parse_date_from_string(event.get('end'))

                    if event_start_date and event_end_date:
                        if not (event_end_date < start_of_current_week or event_start_date > end_of_next_week):
                            has_relevant_event = True
                            break
                except:
                    continue

            if has_relevant_event:
                relevant_projects.append(project)

        print(
            f"--- DEBUG: Vor Filterung: {len(timeline_projects)} Projekte. Nach Filterung: {len(relevant_projects)} Projekte. ---")
        timeline_projects = relevant_projects

    else:
        print("--- DEBUG: view_mode ist nicht 'static', keine Filterung der Projektliste. ---")

    # --- ENDE DER FILTERLOGIK ---

    # Der Rest der Funktion bleibt unverändert
    if not ALL_EMPLOYEES: load_and_prepare_qualifications_data()
    if not ALL_QUALIFICATIONS: load_and_prepare_qualifications_data()
    if not ALL_COMMENTS: load_all_comments()

    all_locations_full = load_locations_and_events()
    visible_cell_ids = timeline_config.get('visible_test_cells', [])
    visible_locations = [loc for loc in all_locations_full if loc.get('ID') in visible_cell_ids]

    manual_assignments_file = timeline_config.get('manual_assignments_file')
    manual_assignments = load_assigned_employees(manual_assignments_file) if manual_assignments_file else []

    comment_counts = Counter(c.get('project_id') for c in ALL_COMMENTS)

    projects_copy = [p.copy() for p in timeline_projects if p]
    for project in projects_copy:
        project_id = f"{project.get('project_code')}_{project.get('engine_serial')}"
        project['comment_count'] = comment_counts.get(project_id, 0)
        safe_id = re.sub(r'[^a-zA-Z0-9_-]', '', project_id)
        status_path = os.path.join(PROJECT_STATUS_DIR, f'project_status_{safe_id}.json')
        if os.path.exists(status_path):
            try:
                with open(status_path, 'r', encoding='utf-8') as f:
                    project['progress_percent'] = json.load(f).get('progress_percent', 0)
            except Exception:
                project['progress_percent'] = 0
        else:
            project['progress_percent'] = 0
        project_events = []
        for i, evt_data in enumerate(project.get('events', [])):
            if not isinstance(evt_data, dict): continue
            if 'id' not in evt_data or not evt_data['id']:
                evt_data['id'] = f"{project_id}_{evt_data.get('title', 'event')}_{i}"
            start_dt = get_datetime_from_date_shift(parse_date_from_string(evt_data.get('start')),
                                                    evt_data.get('start_shift'))
            end_dt = get_datetime_from_date_shift(parse_date_from_string(evt_data.get('end')),
                                                  evt_data.get('end_shift'))
            if start_dt and end_dt:
                evt_data['start_datetime'] = start_dt.isoformat()
                evt_data['end_datetime'] = end_dt.isoformat()
                project_events.append(evt_data)
        project['events'] = project_events

    employee_qualifications_map = {q['id'].lower(): q for q in ALL_QUALIFICATIONS if q.get('id')}
    rules_file_path = timeline_config.get('assignment_rules_file')
    assignment_rules = load_assignment_rules(rules_file_path) if rules_file_path else []

    qualification_statuses_for_tasks = {}
    for project in projects_copy:
        for event in project.get('events', []):
            event_id = event.get('id')
            if not event_id: continue
            qualification_statuses_for_tasks[event_id] = {}
            for employee in ALL_EMPLOYEES:
                emp_id = employee.get('id')
                if not emp_id: continue
                emp_quals = employee_qualifications_map.get(emp_id.lower())
                status = get_employee_qualification_status_by_rules(emp_quals, project, event.get('title'),
                                                                    assignment_rules)
                if status:
                    qualification_statuses_for_tasks[event_id][emp_id.lower()] = status

    today = date.today()
    timeline_start_date = (today - timedelta(days=today.weekday())) - timedelta(weeks=3)
    num_days = 10 * 7

    assignment_groups_for_timeline = timeline_config.get('assignment_groups', ['FF'])
    available_kit_ids = timeline_config.get('available_hardware_kits', [])
    process_templates = load_process_templates()
    manual_hw_assignments = load_hardware_assignments(timeline_id)

    calculated_data = calculate_timeline_assignments(
        projects_copy, ALL_EMPLOYEES, ALL_QUALIFICATIONS,
        manual_assignments, timeline_start_date, num_days,
        assignment_rules=assignment_rules,
        assignment_groups=assignment_groups_for_timeline,
        selected_test_cell_ids=visible_cell_ids,
        timeline_id=timeline_id,
        previous_global_assignments=global_scheduler.assignments,
        process_templates=process_templates,
        available_hardware_kits=available_kit_ids,
        manual_hw_assignments=manual_hw_assignments
    )

    # Baue template_event_map für das Frontend (Farben + assigns-Flags)
    engine_types_in_projects = list({p.get('engine_type', '') for p in projects_copy if p.get('engine_type')})
    template_event_map = {}
    for eng in engine_types_in_projects:
        tmpl = find_best_template_for_engine(eng, process_templates)
        if not tmpl:
            continue
        event_map = {}
        for ev_def in tmpl.get('timeline_events', []):
            name = ev_def.get('name', '').strip()
            if not name:
                continue
            assigns = ev_def.get('assigns', {})
            releases = ev_def.get('releases', {})
            event_map[name] = {
                'people': assigns.get('people', True),
                'rooms': assigns.get('rooms', name.upper() == 'TEST'),
                'hardware': assigns.get('hardware', False),
                'releases_hardware': releases.get('hardware', False),
                'color': ev_def.get('color', '#8B5CF6'),
            }
        if event_map:
            template_event_map[eng] = event_map

    # Reichere hardware_assignments mit Kit-Details an
    hw_data = load_test_equipment_data()
    boms = (hw_data or {}).get('boms', {})
    raw_hw = calculated_data.get('hardware_assignments', {})
    enriched_hw = {}
    for event_id, hw_entry in raw_hw.items():
        kid = hw_entry.get('kit_id', '')
        bom = boms.get(kid, {})
        enriched_hw[event_id] = {
            'kit_id': kid,
            'code': bom.get('code', ''),
            'name': bom.get('name', ''),
            'is_manual': hw_entry.get('is_manual', False),
            'status': bom.get('status', ''),
        }

    available_hardware_kits_detail = [
        {'id': kid, 'name': boms[kid].get('name', ''), 'code': boms[kid].get('code', '')}
        for kid in available_kit_ids if kid in boms
    ]

    return jsonify({
        "projects": projects_copy,
        "employees": ALL_EMPLOYEES,
        "qualifications": ALL_QUALIFICATIONS,
        "locations": visible_locations,
        "timeline_config": {
            "start_date": timeline_start_date.isoformat(),
            "num_days": num_days,
            "columns_per_day": 2,
            "assignment_groups": assignment_groups_for_timeline
        },
        "calculated_data": calculated_data,
        "manual_assignments": manual_assignments,
        "qualification_statuses_for_tasks": qualification_statuses_for_tasks,
        "template_event_map": template_event_map,
        "hardware_assignments": enriched_hw,
        "available_hardware_kits": available_hardware_kits_detail,
    })


@app.route('/test_cell_management')
@login_required
def serve_test_cell_management_page():
    config = load_app_config()
    timelines = config.get('timelines', [])
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    return render_template_string(
        TEST_CELL_MANAGEMENT_HTML_CONTENT,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        timelines=timelines,
        active_page='test_cells',
        url_for=url_for,
        session=session
    )


@app.route('/api/test_cells', methods=['GET'])
@login_required
def api_get_test_cells():
    try:
        locations_with_manual_events = load_locations_and_events()
        locations_with_manual_events = inject_dependency_blocks(locations_with_manual_events)

        all_test_cell_ids = [loc.get('ID') for loc in locations_with_manual_events if loc.get('ID')]

        # 2. Lade ALLE Projekte aus ALLEN konfigurierten Quellen
        all_projects = project_manager.get_all_active_projects()

        # 3. Lade alle globalen Daten, die für die Berechnung benötigt werden
        if not ALL_EMPLOYEES: load_and_prepare_qualifications_data()
        if not ALL_QUALIFICATIONS: load_and_prepare_qualifications_data()

        assignment_rules_to_use = []  # Fürs Cockpit brauchen wir keine spezifischen Regeln

        # 4. Bereite Projekte für die Berechnungsfunktion vor
        projects_for_calc = [p.copy() for p in all_projects if p]
        for project in projects_for_calc:
            project_id_prefix = f"{project.get('project_code')}_{project.get('engine_serial')}"
            events_with_dt = []
            for i, evt in enumerate(project.get('events', [])):
                if not isinstance(evt, dict): continue
                if 'id' not in evt or not evt['id']:
                    evt['id'] = f"{project_id_prefix}_{evt.get('title', 'event')}_{i}"
                start_dt = get_datetime_from_date_shift(parse_date_from_string(evt.get('start')),
                                                        evt.get('start_shift'))
                end_dt = get_datetime_from_date_shift(parse_date_from_string(evt.get('end')), evt.get('end_shift'))
                if start_dt and end_dt:
                    evt['start_datetime'] = start_dt.isoformat()
                    evt['end_datetime'] = end_dt.isoformat()
                    events_with_dt.append(evt)
            project['events'] = events_with_dt

        # 5. Führe eine vollständige, globale Berechnung durch (wie im Originalcode)
        today = date.today()
        timeline_start_date = (today - timedelta(days=today.weekday())) - timedelta(weeks=10)  # Großer Zeitraum
        num_days = 20 * 7

        calculated_data = calculate_timeline_assignments(
            projects=projects_for_calc,
            employees=ALL_EMPLOYEES,
            qualifications=ALL_QUALIFICATIONS,
            manual_assignments=[],  # Manuelle Zuweisungen sind hier nicht relevant für die Testzellen-Planung
            timeline_start_date=timeline_start_date,
            num_days=num_days,
            assignment_rules=assignment_rules_to_use,  # Leere Liste ist ok
            assignment_groups=['FF', 'IG', 'IT'],  # Nimm alle bekannten Gruppen
            selected_test_cell_ids=all_test_cell_ids  # WICHTIG: Gib alle Zellen zur Planung frei
        )

        # 6. Füge die berechnete Zuweisung zu den Projekt-Events hinzu (wie im Originalcode)
        assignments = calculated_data.get('assignments', {})
        for proj in projects_for_calc:
            for evt in proj.get('events', []):
                if evt.get('title', '').lower() == 'test':
                    # Finde die erste Zuweisung für dieses Event
                    for i in range(100):  # Sicherheitslimit
                        event_slot_key = f"{evt.get('id')}_{i}"
                        if event_slot_key in assignments:
                            assignment_info = assignments[event_slot_key]
                            if assignment_info and assignment_info.get("test_cell_id"):
                                evt['assigned_test_cell_id'] = assignment_info.get("test_cell_id")
                                break
                        else:
                            break

        # 7. Reichere die Daten mit den "Operational" Events an (wie im Originalcode)
        all_aggregated_locations = aggregate_operational_events(locations_with_manual_events, projects_for_calc)

        return jsonify(all_aggregated_locations)

    except Exception as e:
        print(f"!!! KRITISCHER FEHLER in api_get_test_cells (Original-Pattern Version): {e}")
        traceback.print_exc()
        return jsonify({"error": "Interner Serverfehler beim Laden der Testzellen-Daten."}), 500


@app.route('/api/test_cells', methods=['POST'])
@login_required
def api_save_test_cells():
    user_roles = session.get('roles', [])
    data = request.get_json()
    if data is None:
        return jsonify({"status": "error", "message": "Invalid JSON"}), 400

    # Lade die Originaldaten, um zu prüfen, welche Art von Änderung vorliegt
    original_data = load_locations_and_events()
    original_cell_ids = {cell['ID'] for cell in original_data}
    new_cell_ids = {cell['ID'] for cell in data}

    # Prüfen, ob Stammdaten (Zellen selbst) geändert/hinzugefügt/gelöscht wurden
    is_master_data_changed = (original_cell_ids != new_cell_ids) or \
                             any(c1['TESTCELL'] != c2.get('TESTCELL', '') or set(c1.get('TYPES', [])) != set(
                                 c2.get('TYPES', []))
                                 for c1 in original_data for c2 in data if c1['ID'] == c2.get('ID'))

    # Führe Berechtigungsprüfungen durch
    if is_master_data_changed:
        if Roles.ADMIN not in user_roles and Roles.PLANNER not in user_roles:
            return jsonify(
                {"status": "error", "message": "Keine Berechtigung zum Verwalten von Testzellen-Stammdaten."}), 403

    # Für Event-Änderungen (die immer passieren können)
    can_manage_events = any(role in user_roles for role in
                            [Roles.ADMIN, Roles.PLANNER, Roles.TESTENGINEER, Roles.MAINTENANCEMANAGER,
                             Roles.TESTCELLINSPECTOR])
    if not can_manage_events and not is_master_data_changed:
        # Wenn nur Events geändert wurden, aber keine Berechtigung dafür da ist
        return jsonify({"status": "error", "message": "Keine Berechtigung zum Verwalten von Events."}), 403

    if save_locations_and_events(data):
        return jsonify({"status": "success", "message": "Daten erfolgreich gespeichert."}), 200
    else:
        return jsonify({"status": "error", "message": "Fehler beim Speichern der CSV-Dateien."}), 500


@app.route('/api/comments/<project_id>', methods=['POST'])
@login_required
def add_comment(project_id):
    data = request.json
    if not data or 'comment' not in data or not data['comment'].strip():
        return jsonify({"error": "Kommentar darf nicht leer sein."}), 400

    import uuid
    new_comment = {
        "comment_id": f"comm_{uuid.uuid4().hex[:8]}",
        "project_id": project_id,
        "author": get_current_username_from_session(),
        "timestamp": datetime.now().isoformat(),
        "comment": data['comment'].strip()
    }

    ALL_COMMENTS.append(new_comment)
    if save_all_comments():
        return jsonify(new_comment), 201
    else:
        ALL_COMMENTS.pop()
        return jsonify({"error": "Fehler beim Speichern des Kommentars."}), 500


@app.route('/api/comments/entry/<comment_id>', methods=['PUT'])
@login_required
def update_comment(comment_id):
    data = request.json
    if not data or 'comment' not in data or not data['comment'].strip():
        return jsonify({"error": "Kommentar darf nicht leer sein."}), 400

    comment_to_update = next((c for c in ALL_COMMENTS if c.get('comment_id') == comment_id), None)

    if not comment_to_update:
        return jsonify({"error": "Kommentar nicht gefunden."}), 404

    if comment_to_update.get('author') != get_current_username_from_session():
        return jsonify({"error": "Keine Berechtigung zum Bearbeiten dieses Kommentars."}), 403

    comment_to_update['comment'] = data['comment'].strip()
    comment_to_update['timestamp'] = datetime.now().isoformat()

    if save_all_comments():
        return jsonify(comment_to_update), 200
    else:
        return jsonify({"error": "Fehler beim Speichern der Änderungen."}), 500


@app.route('/api/comments/entry/<comment_id>', methods=['DELETE'])
@login_required
def delete_comment(comment_id):
    global ALL_COMMENTS
    comment_to_delete = next((c for c in ALL_COMMENTS if c.get('comment_id') == comment_id), None)

    if not comment_to_delete:
        return jsonify({"error": "Kommentar nicht gefunden."}), 404

    if comment_to_delete.get('author') != get_current_username_from_session():
        return jsonify({"error": "Keine Berechtigung zum Löschen dieses Kommentars."}), 403

    ALL_COMMENTS = [c for c in ALL_COMMENTS if c.get('comment_id') != comment_id]

    if save_all_comments():
        return jsonify({"message": "Kommentar erfolgreich gelöscht."}), 200
    else:
        ALL_COMMENTS.append(comment_to_delete)
        return jsonify({"error": "Fehler beim Speichern nach dem Löschen."}), 500


@app.route('/api/comments/<project_id>', methods=['GET'])
@login_required
def get_comments(project_id):
    project_comments = [c for c in ALL_COMMENTS if c.get('project_id') == project_id]
    project_comments.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
    return jsonify(project_comments)


@app.route('/api/manual_assignment/<string:timeline_id>', methods=['POST'])
@login_required
def api_manual_assignment(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)

    if not timeline_config or 'manual_assignments_file' not in timeline_config:
        return jsonify({"error": f"Keine 'manual_assignments_file' für Timeline '{timeline_id}' konfiguriert."}), 404

    assigned_employees_file = timeline_config['manual_assignments_file']

    data = request.json
    if not data or 'slot_info' not in data or 'assignments' not in data:
        return jsonify({"error": "Ungültige Daten"}), 400

    all_manual_assignments = load_assigned_employees(assigned_employees_file)

    new_assignments_for_slot = data['assignments']
    slot_info = data.get('slot_info')

    if slot_info:
        all_manual_assignments = [
            a for a in all_manual_assignments
            if not (
                    a.get('project_code') == slot_info.get('project_code') and
                    a.get('engine_serial') == slot_info.get('engine_serial') and
                    a.get('event_title') == slot_info.get('event_title') and
                    a.get('date_str') == slot_info.get('date_str') and
                    a.get('shift') == slot_info.get('shift')
            )
        ]

    all_manual_assignments.extend(new_assignments_for_slot)

    if save_assigned_employees(assigned_employees_file, all_manual_assignments):
        update_global_schedule()
        message = "Manuelle Zuweisung erfolgreich gespeichert."
        if not new_assignments_for_slot:
            message = "Manuelle Zuweisung erfolgreich gelöscht."
        return jsonify({"message": message}), 200
    else:
        return jsonify({"error": "Fehler beim Speichern der Zuweisungsdatei."}), 500


@app.route('/api/clear_orphaned_manual_assignments/<string:timeline_id>', methods=['POST'])
@login_required
def api_clear_orphaned_manual_assignments(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if not timeline_config or 'manual_assignments_file' not in timeline_config:
        return jsonify({"error": f"Keine 'manual_assignments_file' für Timeline '{timeline_id}' konfiguriert."}), 404
    assigned_employees_file = timeline_config['manual_assignments_file']
    data = request.json or {}
    entries_to_delete = data.get('entries_to_delete', [])
    if not entries_to_delete:
        return jsonify({"message": "Nichts zu löschen.", "deleted_count": 0}), 200
    delete_keys = set()
    for e in entries_to_delete:
        key = (e.get('project_code', ''), e.get('engine_serial', ''), e.get('event_title', ''),
               e.get('date_str', ''), e.get('shift', ''), e.get('employee_id', '').lower())
        delete_keys.add(key)
    all_assignments = load_assigned_employees(assigned_employees_file)
    original_count = len(all_assignments)
    filtered = [a for a in all_assignments
                if (a.get('project_code', ''), a.get('engine_serial', ''), a.get('event_title', ''),
                    a.get('date_str', ''), a.get('shift', ''), a.get('employee_id', '').lower()) not in delete_keys]
    deleted_count = original_count - len(filtered)
    if save_assigned_employees(assigned_employees_file, filtered):
        update_global_schedule()
        return jsonify({"message": f"{deleted_count} verwaiste Zuweisung(en) gelöscht.", "deleted_count": deleted_count}), 200
    else:
        return jsonify({"error": "Fehler beim Speichern."}), 500


@app.route('/api/update_from_sap/<string:timeline_id>', methods=['POST'])
@login_required
def api_update_from_sap(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if not timeline_config:
        return jsonify({"error": "Timeline-Konfiguration nicht gefunden."}), 404

    # NEU: Direkter Zugriff auf das Filterobjekt
    sap_filter_rules = timeline_config.get('sap_filter')
    target_file = timeline_config.get('data_file')

    # Die Prüfung `if not sap_filter_rules` ist weiterhin sinnvoll
    if not sap_filter_rules or not target_file:
        return jsonify(
            {"error": f"Kein SAP-Filter oder keine Zieldatei für Timeline '{timeline_id}' konfiguriert."}), 400

    sap_csv_path = r"L:\lgroup\@IT_Data\PPS\ZLSGS_Dataexport.csv"
    if not os.path.exists(sap_csv_path):
        sap_csv_path = "ZLSGS_Dataexport.csv"
        if not os.path.exists(sap_csv_path):
            return jsonify({"error": "SAP-Datei nicht gefunden."}), 404

    try:
        current_projects = load_timeline_projects_for_flask([target_file])
        current_project_order = [(p.get('project_code', ''), p.get('engine_serial', '')) for p in current_projects]

        filtered_df = filter_sap_data(sap_csv_path, filter_rules=sap_filter_rules)

        if filtered_df is None or filtered_df.empty:
            return jsonify(
                {"message": "Keine neuen oder relevanten Daten im SAP-Export für diesen Filter gefunden."}), 200

        changes_made = merge_into_timeline_projects(target_file, filtered_df, current_project_order)

        if changes_made:
            return jsonify(
                {"message": f"Timeline '{timeline_config['name']}' erfolgreich mit SAP-Daten aktualisiert."}), 200
        else:
            return jsonify({"message": "Projekte sind bereits auf dem neuesten Stand."}), 200

    except Exception as e:
        traceback.print_exc()
        return jsonify({"error": f"Fehler beim SAP-Update: {str(e)}"}), 500


@app.route('/api/save_editable_timeline_data/<string:timeline_id>', methods=['POST'])
@login_required
def api_save_editable_timeline_data(timeline_id):
    config = load_app_config()
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if not timeline_config or 'data_file' not in timeline_config:
        return jsonify({"error": "Timeline-Konfiguration zum Speichern nicht gefunden."}), 404

    data_from_client = request.json
    if not data_from_client or 'projects' not in data_from_client:
        return jsonify({"error": "Ungültige Daten erhalten."}), 400

    projects_to_save = data_from_client['projects']
    target_file = timeline_config['data_file']

    # --- GARBAGE COLLECTION & ARCHIVIERUNG ---
    today = date.today()
    # Stichtag: Alles was älter als 4 Wochen (28 Tage) ist, wird aussortiert
    cutoff_date = today - timedelta(weeks=4)

    cleaned_projects = []
    archived_count = 0
    deleted_count = 0

    # Sicherstellen, dass der Ordner für JSONs existiert
    os.makedirs(PROJECT_STATUS_DIR, exist_ok=True)

    for p in projects_to_save:
        # 1. Finde das späteste Enddatum aller Events in diesem Projekt
        latest_date = None
        for evt in p.get('events', []):
            end_date_obj = parse_date_from_string(evt.get('end', ''))
            if end_date_obj:
                if not latest_date or end_date_obj > latest_date:
                    latest_date = end_date_obj

        # Fallback: Wenn keine Events da sind, schau auf TS (Startdatum)
        if not latest_date:
            ts_str = p.get('TS', '')
            try:
                if ts_str:
                    # Parse z.B. "25.02.2026 08:00:00"
                    latest_date = datetime.strptime(ts_str, "%d.%m.%Y %H:%M:%S").date()
            except:
                pass

        # 2. Prüfen: Ist das Projekt alt genug zum Aufräumen?
        if latest_date and latest_date < cutoff_date:

            if p.get('active'):
                # FALL A: Aktiv -> ARCHIVIEREN
                project_code = p.get('project_code', 'UNKNOWN')
                engine_serial = p.get('engine_serial', 'UNKNOWN')
                safe_project_id = re.sub(r'[^a-zA-Z0-9_-]', '', f"{project_code}_{engine_serial}")
                status_file_path = os.path.join(PROJECT_STATUS_DIR, f'project_status_{safe_project_id}.json')

                archive_data = None
                if os.path.exists(status_file_path):
                    try:
                        with open(status_file_path, 'r', encoding='utf-8') as f:
                            archive_data = json.load(f)
                    except:
                        pass

                # Falls im Maintenance Center noch keine JSON erstellt wurde, generieren wir sie jetzt
                if not archive_data:
                    archive_data = get_or_create_project_status_details(p)

                if archive_data:
                    # Planungs-Events in die JSON pressen (Digitaler Zwilling!)
                    archive_data['archived_planning_events'] = p.get('events', [])
                    archive_data['origin_timeline'] = timeline_id
                    archive_data['system_archived_at'] = today.isoformat()

                    save_json_file(status_file_path, archive_data)
                    archived_count += 1

                # WICHTIG: Das Projekt wird NICHT an cleaned_projects angehängt (fliegt also aus der CSV)

            else:
                # FALL B: Inaktiv -> EINFACH LÖSCHEN
                deleted_count += 1
                # WICHTIG: Fliegt ebenfalls aus der CSV, ohne JSON-Update

        else:
            # FALL C: Projekt ist noch aktuell (oder hat kein Datum) -> Behalten
            cleaned_projects.append(p)
    # ----------------------------------------

    # 3. Speichern der bereinigten Liste in die CSV
    if project_manager.save_projects(cleaned_projects, timeline_id):
        update_global_schedule()

        # Dynamische Erfolgsmeldung für den User bauen
        msg = "Änderungen erfolgreich gespeichert."
        if archived_count > 0 or deleted_count > 0:
            msg += f" Aufräumaktion: {archived_count} Projekt(e) archiviert, {deleted_count} inaktive gelöscht."

        return jsonify({"message": msg}), 200
    else:
        return jsonify({"error": "Änderungen nicht erfolgreich gespeichert."}), 500


@app.route('/api/get_editable_timeline_data')
@login_required
def api_get_editable_timeline_data():
    return api_get_unified_timeline_data()


@app.route('/timeline/edit/<string:timeline_id>')
@login_required
def serve_editable_timeline_page(timeline_id):
    config = load_app_config()
    timelines = config.get('timelines', [])
    timeline_config = next(
        (tl for tl in config.get('timelines', []) if tl['id'] == timeline_id and tl['type'] == 'editable'), None)
    if not timeline_config:
        abort(404)

    timeline_name_for_display = timeline_config.get('name', 'Timeline Bearbeitung')
    user_initials = "N/A"  # NEU
    vorname = session.get('user_vorname', '')  # NEU
    nachname = session.get('user_nachname', '')  # NEU
    if vorname and nachname: user_initials = (vorname[0] + nachname[0]).upper()  # NEU

    return render_template_string(
        EDITABLE_TIMELINE_HTML_CONTENT,
        current_user_display_name=get_current_username_from_session(),
        timelines=timelines,
        timeline_id=timeline_id,
        timeline_name=timeline_name_for_display,
        current_user_initials=user_initials,
        active_page='timelines',
    )


def _user_can_edit_attendance():
    roles = session.get('roles', [])
    return session.get('is_admin', False) or 'ADMIN' in roles or 'PLANNER' in roles


@app.route('/attendance_planning')
@login_required
def serve_attendance_planning_page():
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()
    config = load_app_config()
    timelines = config.get('timelines', [])
    return render_template_string(
        ATTENDANCE_PLANNING_HTML,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        timelines=timelines,
        active_page='attendance_planning',
        url_for=url_for,
        session=session,
        can_edit=_user_can_edit_attendance()
    )


def _compute_projected_shift(ref_week_key, ref_shift, target_week_key, shift_model, pattern=None):
    """Deterministic projection from a fixed reference week — not from last explicit entry."""
    if shift_model == 'none' or not ref_week_key:
        return ''
    if shift_model == 'fixed_F':
        return 'F'
    if shift_model == 'fixed_S':
        return 'S'
    if shift_model == 'fixed_N':
        return 'N'
    try:
        y1, w1 = ref_week_key.split('-W')
        y2, w2 = target_week_key.split('-W')
        d1 = date.fromisocalendar(int(y1), int(w1), 1)
        d2 = date.fromisocalendar(int(y2), int(w2), 1)
        delta = (d2 - d1).days // 7
    except Exception:
        return ''
    if delta < 0:
        return ''  # Before reference date — no projection
    if pattern:
        return pattern[delta % len(pattern)]
    if shift_model == 'alternating_FS':
        if not ref_shift:
            return ''
        rs = ref_shift.upper()
        if rs not in ('F', 'S'):
            rs = 'F'
        return rs if delta % 2 == 0 else ('S' if rs == 'F' else 'F')
    return ''


@app.route('/api/attendance/data')
@login_required
def api_attendance_data():
    week_offset = int(request.args.get('week_offset', 0))
    num_weeks = int(request.args.get('num_weeks', 4))
    holidays_map = get_holidays_set()

    today = date.today()
    start_of_current_week = today - timedelta(days=today.weekday())
    start_date = start_of_current_week + timedelta(weeks=week_offset)

    weeks = []
    for i in range(num_weeks):
        week_start = start_date + timedelta(weeks=i)
        iso_year, iso_week, _ = week_start.isocalendar()
        week_key = f"{iso_year}-W{iso_week:02d}"
        days = []
        for j in range(7):  # Mo–So
            d = week_start + timedelta(days=j)
            d_str = d.isoformat()
            holiday_name = holidays_map.get(d_str)
            is_weekend = j >= 5
            days.append({
                'date': d_str,
                'dow': ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'][j],
                'holiday': bool(holiday_name),
                'holiday_name': holiday_name or '',
                'is_weekend': is_weekend
            })
        weeks.append({'week_key': week_key, 'kw_num': iso_week, 'year': iso_year, 'days': days})

    raw = load_raw_attendance_data()
    groups = load_attendance_groups()
    library = {m['id']: m for m in raw.get('shift_model_library', [])}

    employees_out = []
    for emp in raw.get('employees', []):
        schedule = emp.get('schedule', {})
        shift_model = emp.get('shift_model', 'alternating_FS')
        ref_wkey = emp.get('shift_model_ref_week', '')
        ref_shift = emp.get('shift_model_ref_shift', '')
        pattern = library[shift_model]['pattern'] if shift_model in library else None

        schedule_out = {}
        for week in weeks:
            wkey = week['week_key']
            wdata = schedule.get(wkey, {})
            if wkey in schedule:
                schedule_out[wkey] = {
                    'shift': wdata.get('shift', ''),
                    'days': wdata.get('days', {}),
                    'projected': False
                }
            else:
                proj_shift = _compute_projected_shift(ref_wkey, ref_shift, wkey, shift_model, pattern)
                schedule_out[wkey] = {
                    'shift': proj_shift,
                    'days': {},
                    'projected': bool(proj_shift)
                }
        employees_out.append({
            'id': emp.get('id', ''),
            'name': emp.get('name', ''),
            'initials': emp.get('initials', ''),
            'group_id': emp.get('group_id', ''),
            'shift_model': emp.get('shift_model', 'alternating_FS'),
            'shift_model_ref_week': ref_wkey,
            'schedule': schedule_out
        })

    if not _user_can_edit_attendance():
        for emp in employees_out:
            for wdata in emp['schedule'].values():
                for date_str, day in wdata.get('days', {}).items():
                    if isinstance(day, dict) and day.get('ereignis', '').strip():
                        day['ereignis'] = '●'

    return jsonify({
        'weeks': weeks,
        'employees': employees_out,
        'groups': groups,
        'shift_model_library': list(library.values())
    })


@app.route('/api/attendance/update', methods=['POST'])
@login_required
def api_attendance_update():
    if not _user_can_edit_attendance():
        return jsonify({'error': 'Keine Berechtigung'}), 403
    changes = request.json.get('changes', {})
    if not changes:
        return jsonify({'error': 'Keine Änderungen'}), 400
    holidays_map = get_holidays_set()
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        emp_map = {emp.get('id', ''): emp for emp in data.get('employees', []) if emp.get('id')}
        for emp_id, week_changes in changes.items():
            emp = emp_map.get(emp_id)
            if not emp:
                continue
            if 'schedule' not in emp:
                emp['schedule'] = {}
            for week_key, wdata in week_changes.items():
                if week_key not in emp['schedule']:
                    # Auto-fill Mon-Fri so extension logic has valid days to work with
                    try:
                        year_s, w_s = week_key.split('-W')
                        monday = date.fromisocalendar(int(year_s), int(w_s), 1)
                        prefilled_days = {}
                        for offset in range(5):
                            d = monday + timedelta(days=offset)
                            prefilled_days[d.isoformat()] = {'ereignis': '', 'holiday': d.isoformat() in holidays_map}
                        emp['schedule'][week_key] = {'shift': '', 'days': prefilled_days}
                    except Exception:
                        emp['schedule'][week_key] = {'shift': '', 'days': {}}
                if 'shift' in wdata:
                    emp['schedule'][week_key]['shift'] = wdata['shift']
                for day_str, day_data in wdata.get('days', {}).items():
                    if day_data is None:
                        emp['schedule'][week_key]['days'].pop(day_str, None)
                    else:
                        emp['schedule'][week_key]['days'][day_str] = {
                            'ereignis': day_data.get('ereignis', ''),
                            'holiday': day_str in holidays_map
                        }
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    load_employees_from_attendance_json()
    return jsonify({'ok': True})


@app.route('/api/attendance/groups', methods=['GET'])
@login_required
def api_attendance_groups_get():
    return jsonify(load_attendance_groups())


@app.route('/api/attendance/groups', methods=['POST'])
@admin_required
def api_attendance_groups_post():
    payload = request.json
    gid = payload.get('id', '').strip()
    name = payload.get('name', '').strip()
    color = payload.get('color', '#6b7280')
    editing = payload.get('editing')
    if not gid or not name:
        return jsonify({'error': 'id und name erforderlich'}), 400
    with _get_file_lock(ATTENDANCE_GROUPS_FILE):
        groups = _load_json_file_unlocked(ATTENDANCE_GROUPS_FILE, [])
        if editing:
            for g in groups:
                if g['id'] == editing:
                    g['name'] = name
                    g['color'] = color
                    break
        else:
            if any(g['id'] == gid for g in groups):
                return jsonify({'error': f'Gruppe {gid} existiert bereits'}), 409
            groups.append({'id': gid, 'name': name, 'color': color})
        _write_json_file_unlocked(ATTENDANCE_GROUPS_FILE, groups)
    return jsonify({'ok': True})


@app.route('/api/attendance/groups/<group_id>', methods=['DELETE'])
@admin_required
def api_attendance_groups_delete(group_id):
    with _get_file_lock(ATTENDANCE_GROUPS_FILE):
        groups = _load_json_file_unlocked(ATTENDANCE_GROUPS_FILE, [])
        groups = [g for g in groups if g['id'] != group_id]
        _write_json_file_unlocked(ATTENDANCE_GROUPS_FILE, groups)
    # Cascade: Gruppe aus allen Timeline assignment_groups in config.json entfernen
    config = load_app_config()
    changed = False
    for tl in config.get('timelines', []):
        before = tl.get('assignment_groups', [])
        after = [g for g in before if g != group_id]
        if after != before:
            tl['assignment_groups'] = after
            changed = True
            print(f"INFO: Gruppe '{group_id}' aus Timeline '{tl.get('id')}' assignment_groups entfernt.")
    if changed:
        save_app_config(config)
    return jsonify({'ok': True})


@app.route('/api/attendance/employees', methods=['GET'])
@login_required
def api_attendance_employees():
    data = load_raw_attendance_data()
    return jsonify([{'id': e.get('id', ''), 'name': e.get('name', ''), 'group_id': e.get('group_id', '')} for e in data.get('employees', [])])


@app.route('/api/attendance/employees/<emp_id>', methods=['DELETE'])
@admin_required
def api_attendance_employees_delete(emp_id):
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        data['employees'] = [e for e in data.get('employees', []) if e.get('id') != emp_id]
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    load_employees_from_attendance_json()
    return jsonify({'ok': True})


@app.route('/api/attendance/shift_model_library', methods=['GET'])
@login_required
def api_shift_model_library_get():
    data = load_raw_attendance_data()
    return jsonify(data.get('shift_model_library', []))


@app.route('/api/attendance/shift_model_library', methods=['POST'])
@admin_required
def api_shift_model_library_create():
    payload = request.json or {}
    name = (payload.get('name') or '').strip()
    pattern = payload.get('pattern', [])
    if not name:
        return jsonify({'error': 'Name erforderlich'}), 400
    if not pattern or not all(p in ('F', 'S', 'N', '') for p in pattern):
        return jsonify({'error': 'Ungültiges Muster'}), 400
    model_id = 'lib_' + uuid.uuid4().hex[:8]
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        data.setdefault('shift_model_library', []).append({'id': model_id, 'name': name, 'pattern': pattern})
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    return jsonify({'ok': True, 'id': model_id})


@app.route('/api/attendance/shift_model_library/<model_id>', methods=['PUT'])
@admin_required
def api_shift_model_library_update(model_id):
    payload = request.json or {}
    name = (payload.get('name') or '').strip()
    pattern = payload.get('pattern', [])
    if not name:
        return jsonify({'error': 'Name erforderlich'}), 400
    if not pattern or not all(p in ('F', 'S', 'N', '') for p in pattern):
        return jsonify({'error': 'Ungültiges Muster'}), 400
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        model = next((m for m in data.get('shift_model_library', []) if m['id'] == model_id), None)
        if not model:
            return jsonify({'error': 'Modell nicht gefunden'}), 404
        model['name'] = name
        model['pattern'] = pattern
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    load_employees_from_attendance_json()
    return jsonify({'ok': True})


@app.route('/api/attendance/shift_model_library/<model_id>', methods=['DELETE'])
@admin_required
def api_shift_model_library_delete(model_id):
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        data['shift_model_library'] = [m for m in data.get('shift_model_library', []) if m['id'] != model_id]
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    return jsonify({'ok': True})


@app.route('/api/attendance/employees/<emp_id>/group', methods=['PUT'])
@admin_required
def api_attendance_employees_set_group(emp_id):
    group_id = (request.json or {}).get('group_id', '')
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        emp = next((e for e in data.get('employees', []) if e.get('id') == emp_id), None)
        if not emp:
            return jsonify({'error': 'Mitarbeiter nicht gefunden'}), 404
        emp['group_id'] = group_id
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    load_employees_from_attendance_json()

    # Quali-CSV synchronisieren: Gruppenfeld mitaktualisieren
    global ALL_QUALIFICATIONS, MERKMALE_IN_ORDER
    for q in ALL_QUALIFICATIONS:
        if q.get('id', '').lower() == emp_id.lower():
            q['quali'] = group_id
            break
    save_qualifications_to_csv(QUALIFICATIONS_FILE_PATH, ALL_QUALIFICATIONS, MERKMALE_IN_ORDER, silent=True)

    return jsonify({'ok': True})


@app.route('/api/attendance/employees/<emp_id>/shift_model', methods=['PUT'])
@admin_required
def api_attendance_employees_set_shift_model(emp_id):
    builtin_models = ('alternating_FS', 'fixed_F', 'fixed_S', 'fixed_N', 'none')
    payload = request.json or {}
    model = payload.get('shift_model', 'alternating_FS')
    ref_week = payload.get('ref_week')
    ref_shift = payload.get('ref_shift')
    with _get_file_lock(ATTENDANCE_DATA_FILE):
        data = _load_json_file_unlocked(ATTENDANCE_DATA_FILE, {"employees": []})
        library_ids = {m['id'] for m in data.get('shift_model_library', [])}
        if model not in builtin_models and model not in library_ids:
            return jsonify({'error': 'Ungültiges Modell'}), 400
        emp = next((e for e in data.get('employees', []) if e.get('id') == emp_id), None)
        if not emp:
            return jsonify({'error': 'Mitarbeiter nicht gefunden'}), 404
        emp['shift_model'] = model
        if ref_week is not None:
            emp['shift_model_ref_week'] = ref_week
            emp['shift_model_ref_shift'] = (ref_shift or '').upper()
        data['last_updated'] = datetime.now().isoformat()
        _write_json_file_unlocked(ATTENDANCE_DATA_FILE, data)
    load_employees_from_attendance_json()
    return jsonify({'ok': True})


@app.route('/api/attendance/quotas', methods=['GET'])
@login_required
def api_attendance_quotas_get():
    return jsonify(load_vacation_quotas())


@app.route('/api/attendance/quotas', methods=['POST'])
@admin_required
def api_attendance_quotas_post():
    updates = request.json
    with _get_file_lock(ATTENDANCE_QUOTAS_FILE):
        quotas = _load_json_file_unlocked(ATTENDANCE_QUOTAS_FILE, {})
        for emp_id, year_data in updates.items():
            if emp_id not in quotas:
                quotas[emp_id] = {}
            for year, val in year_data.items():
                quotas[emp_id][str(year)] = int(val)
        _write_json_file_unlocked(ATTENDANCE_QUOTAS_FILE, quotas)
    return jsonify({'ok': True})


@app.route('/api/attendance/holidays', methods=['GET'])
@login_required
def api_attendance_holidays():
    year = int(request.args.get('year', date.today().year))
    return jsonify([{'date': d, 'name': n} for d, n in _brandenburg_holidays(year)])


@app.route('/api/attendance/closures', methods=['GET'])
@login_required
def api_attendance_closures_get():
    return jsonify(load_company_closures())


@app.route('/api/attendance/closures', methods=['POST'])
@admin_required
def api_attendance_closures_post():
    payload = request.json or {}
    date_str = payload.get('date', '').strip()
    name = payload.get('name', '').strip()
    if not date_str or not name:
        return jsonify({'error': 'Datum und Name erforderlich'}), 400
    try:
        date.fromisoformat(date_str)
    except ValueError:
        return jsonify({'error': 'Ungültiges Datum (YYYY-MM-DD)'}), 400
    with _get_file_lock(ATTENDANCE_CLOSURES_FILE):
        closures = _load_json_file_unlocked(ATTENDANCE_CLOSURES_FILE, [])
        if any(c['date'] == date_str for c in closures):
            return jsonify({'error': 'Dieser Tag ist bereits eingetragen'}), 409
        closures.append({'date': date_str, 'name': name})
        _write_json_file_unlocked(ATTENDANCE_CLOSURES_FILE, sorted(closures, key=lambda x: x['date']))
    return jsonify({'ok': True})


@app.route('/api/attendance/closures/<date_str>', methods=['DELETE'])
@admin_required
def api_attendance_closures_delete(date_str):
    with _get_file_lock(ATTENDANCE_CLOSURES_FILE):
        closures = [c for c in _load_json_file_unlocked(ATTENDANCE_CLOSURES_FILE, []) if c['date'] != date_str]
        _write_json_file_unlocked(ATTENDANCE_CLOSURES_FILE, closures)
    return jsonify({'ok': True})


@app.route('/api/free-days')
@login_required
def api_free_days():
    """Returns all non-working days (public holidays + company closures) as a sorted list of date strings."""
    return jsonify(sorted(get_holidays_set().keys()))


@app.route('/employee_presence')
@login_required
def serve_employee_presence_page():
    return redirect(url_for('serve_attendance_planning_page'))


@app.route('/api/employee_presence_data')
@login_required
def api_get_employee_presence_data():
    week_offset = request.args.get('offset', default=0, type=int)
    num_weeks = request.args.get('num_weeks', default=2, type=int)

    global ALL_EMPLOYEES, MERKMALE_IN_ORDER
    if not ALL_EMPLOYEES:
        load_and_prepare_qualifications_data()

    presence_data_for_web = get_employee_presence_data_for_web(ALL_EMPLOYEES, current_week_offset=week_offset,
                                                               num_weeks_to_display=num_weeks)
    return jsonify(presence_data_for_web)


@app.route('/login', methods=['GET', 'POST'])
def login_page():
    error = None
    if request.method == 'POST':
        user_id_input = request.form.get('username', '').strip().lower()
        password_input = request.form.get('password')
        user_data = next((user for user in ALL_USERS if user['id'] == user_id_input), None)
        if user_data and user_data['password'] == password_input:
            session['user_id'] = user_data['id']

            session['roles'] = user_data.get('roles', [Roles.OBSERVER])

            # 2. Alte Session-Variablen für Abwärtskompatibilität setzen
            session['is_admin'] = Roles.ADMIN in session['roles']
            # WICHTIG: Definiere hier, welche neuen Rollen die alte 'can_change_entries' Berechtigung abbilden.
            # Für den Anfang nehmen wir an, dass 'PLANNER' und 'TESTENGINEER' dies tun.
            session['can_change_entries'] = any(role in session['roles'] for role in
                                                [Roles.PLANNER, Roles.TESTENGINEER, Roles.MAINTENANCEMANAGER,
                                                 Roles.HARDWAREMANAGER])

            vorname = user_data.get('vorname', '')
            nachname = user_data.get('nachname', '')
            if vorname and nachname:
                session['username_display'] = f"{vorname[0]}. {nachname}"
            elif nachname:
                session['username_display'] = nachname
            elif vorname:
                session['username_display'] = vorname
            else:
                session['username_display'] = user_data['id'].upper()

            session['username_actual'] = user_data['id']
            session['user_nachname'] = user_data.get('nachname', '')
            session['user_vorname'] = user_data.get('vorname', '')

            return redirect(request.args.get('next') or url_for('index'))
        error = 'Ungültige Benutzer-ID oder Passwort.'
    return render_template_string(LOGIN_PAGE_HTML, error=error, request=request, url_for=url_for)


@app.route('/logout')
def logout_page():
    session.pop('user_id', None);
    session.pop('username_display', None)
    session.pop('is_admin', None);
    session.pop('can_change_entries', None)
    session.pop('username_actual', None)
    return redirect(url_for('login_page'))


@app.route('/api/project_details/<path:project_name_url>')
@login_required
def api_get_project_details(project_name_url):
    project_name = unquote(project_name_url)
    global LOGGED_IN_USER_DISPLAY_NAME_FOR_PYTHON
    LOGGED_IN_USER_DISPLAY_NAME_FOR_PYTHON = get_current_username_from_session()

    try:
        # 1. Lade die Konfiguration, um alle möglichen Projektdateien zu kennen
        config = load_app_config()
        all_possible_data_files = set()
        for tl in config.get('timelines', []):
            if 'data_file' in tl:
                all_possible_data_files.add(tl['data_file'])
            if 'data_files' in tl:
                for f in tl['data_files']:
                    all_possible_data_files.add(f)

        # 2. Lade alle Projekte aus allen konfigurierten Dateien
        all_known_projects = load_timeline_projects_for_flask(list(all_possible_data_files))

        # 3. Suche das spezifische Projekt in der frisch geladenen Gesamtliste (Live-CSV)
        project_to_detail = next((p for p in all_known_projects if p.get('name') == project_name), None)

        if not project_to_detail:
            # --- NEU: FALLBACK INS ARCHIV (JSON) ---
            print(f"INFO: Projekt '{project_name}' nicht in Live-CSV gefunden. Suche im Archiv...")
            if os.path.exists(PROJECT_STATUS_DIR):
                for filename in os.listdir(PROJECT_STATUS_DIR):
                    if filename.endswith('.json'):
                        filepath = os.path.join(PROJECT_STATUS_DIR, filename)
                        try:
                            with open(filepath, 'r', encoding='utf-8') as f:
                                archive_data = json.load(f)
                                if archive_data.get('name') == project_name:
                                    # Treffer im Archiv!
                                    # Wir fügen noch die 'has_document' Flags hinzu, wie beim Live-Projekt
                                    templates = load_process_templates()
                                    engine_type = archive_data.get('engine_type')
                                    template = find_best_template_for_engine(engine_type, templates)
                                    doc_map = {step['name']: step['document_template'] for step in
                                               template.get('steps', [])}

                                    for step in archive_data.get('steps', []):
                                        step['has_document'] = bool(doc_map.get(step['name']))

                                    # Spezielles Flag, damit das Frontend weiß: "Das ist alt!"
                                    archive_data['is_archived'] = True
                                    archive_project_id = f"{archive_data.get('project_code', '')}_{archive_data.get('engine_serial', '')}"
                                    archive_data['comment_count'] = sum(1 for c in ALL_COMMENTS if c.get('project_id') == archive_project_id)
                                    return jsonify(archive_data)
                        except Exception as e:
                            print(f"Fehler beim Lesen der Archivdatei {filename}: {e}")

            # Wenn auch im Archiv nichts ist:
            return jsonify({"error": f"Projekt '{project_name}' nicht gefunden."}), 404

        # 4. Generiere die Statusdetails (der Rest der Funktion bleibt gleich)
        # Hier wird die Event-Struktur mit 'start_datetime' etc. angereichert
        for i, evt_data in enumerate(project_to_detail.get('events', [])):
            if not isinstance(evt_data, dict): continue
            start_dt = get_datetime_from_date_shift(parse_date_from_string(evt_data.get('start')),
                                                    evt_data.get('start_shift'))
            end_dt = get_datetime_from_date_shift(parse_date_from_string(evt_data.get('end')),
                                                  evt_data.get('end_shift'))
            if start_dt and end_dt:
                evt_data['start_datetime'] = start_dt.isoformat()
                evt_data['end_datetime'] = end_dt.isoformat()

        project_details = get_or_create_project_status_details(project_to_detail)

        if not project_details:
            return jsonify({"error": f"Details für Projekt '{project_name}' konnten nicht generiert werden."}), 500

        # Füge 'has_document' Flag hinzu
        templates = load_process_templates()
        engine_type = project_to_detail.get('engine_type')
        template = find_best_template_for_engine(engine_type, templates)
        doc_map = {step['name']: step['document_template'] for step in template.get('steps', [])}

        for step in project_details.get('steps', []):
            step['has_document'] = bool(doc_map.get(step['name']))

        project_id = f"{project_details.get('project_code', '')}_{project_details.get('engine_serial', '')}"
        project_details['comment_count'] = sum(1 for c in ALL_COMMENTS if c.get('project_id') == project_id)

        return jsonify(project_details)

    except Exception as e:
        print(f"Fehler in api_get_project_details für '{project_name}': {e}")
        traceback.print_exc()
        return jsonify({"error": "Interner Serverfehler beim Abrufen der Projektdetails."}), 500


@app.route('/api/project_status/<path:project_id>', methods=['POST'])
@login_required
def api_save_project_status(project_id):
    user_roles = session.get('roles', [])

    # Definiere alle Rollen, die speichern dürfen
    allowed_roles_to_save = {
        Roles.ADMIN,
        Roles.PLANNER,
        Roles.TESTMECHANIC,
        Roles.TESTCELLINSPECTOR,
        Roles.TESTENGINEER,
        Roles.MAINTENANCEMANAGER,
        Roles.HARDWAREMANAGER
    }

    # Prüfe, ob der User mindestens eine der erlaubten Rollen hat
    if not any(role in user_roles for role in allowed_roles_to_save):
        return jsonify({"error": "Keine Berechtigung zum Speichern des Projektstatus."}), 403

    data_to_save = request.json
    if not data_to_save or 'steps' not in data_to_save:
        return jsonify({"error": "Ungültige Daten empfangen."}), 400

    # Sanitize project_id, um Directory Traversal zu verhindern
    safe_project_id = re.sub(r'[^a-zA-Z0-9_-]', '', project_id)

    # Definiere den Zielordner
    status_dir = os.path.join('db', 'projectstatus')

    # Erstelle den Ordner, falls er nicht existiert
    os.makedirs(status_dir, exist_ok=True)

    # Baue den vollständigen Dateipfad zusammen
    status_file_path = os.path.join(status_dir, f'project_status_{safe_project_id}.json')

    # Setzt origin_timeline falls es fehlt (z.B. wenn nur über die dedizierte Projektseite gespeichert wurde)
    if not data_to_save.get('origin_timeline'):
        origin = find_origin_timeline(data_to_save.get('project_code', ''), data_to_save.get('engine_serial', ''))
        if origin:
            data_to_save['origin_timeline'] = origin

    if save_json_file(status_file_path, data_to_save):
        return jsonify({"message": "Status erfolgreich gespeichert."}), 200
    else:
        return jsonify({"error": "Interner Fehler beim atomaren Speichern der Datei."}), 500


@app.route('/api/project_status/<path:project_id>/instruction', methods=['PATCH'])
@login_required
def api_patch_project_instruction(project_id):
    """Atomic patch: merges a single checklist instruction into the stored project status.
    Prevents lost-update races when multiple users check items concurrently."""
    user_roles = session.get('roles', [])
    allowed_roles_to_save = {
        Roles.ADMIN, Roles.PLANNER, Roles.TESTMECHANIC,
        Roles.TESTCELLINSPECTOR, Roles.TESTENGINEER,
        Roles.MAINTENANCEMANAGER, Roles.HARDWAREMANAGER
    }
    if not any(role in user_roles for role in allowed_roles_to_save):
        return jsonify({"error": "Keine Berechtigung."}), 403

    payload = request.json
    if not payload:
        return jsonify({"error": "Kein Payload."}), 400

    step_id_str = payload.get('step_id_str')
    inst_index = payload.get('inst_index')
    patch = payload.get('patch')  # dict with fields to merge into the instruction

    if step_id_str is None or inst_index is None or patch is None:
        return jsonify({"error": "Fehlende Felder: step_id_str, inst_index, patch."}), 400

    safe_project_id = re.sub(r'[^a-zA-Z0-9_-]', '', project_id)
    status_dir = os.path.join('db', 'projectstatus')
    status_file_path = os.path.join(status_dir, f'project_status_{safe_project_id}.json')

    with _get_file_lock(status_file_path):
        try:
            if not os.path.exists(status_file_path):
                return jsonify({"error": "Projektdatei nicht gefunden."}), 404

            with open(status_file_path, 'r', encoding='utf-8') as f:
                data = json.load(f)

            # Locate the step by id_str (recursive search)
            def find_step(steps, target_id):
                for s in steps:
                    if s.get('id_str') == target_id:
                        return s
                    found = find_step(s.get('children', []), target_id)
                    if found:
                        return found
                return None

            step = find_step(data.get('steps', []), step_id_str)
            if step is None:
                return jsonify({"error": f"Step '{step_id_str}' nicht gefunden."}), 404

            instructions = step.get('instructions', [])
            if not isinstance(inst_index, int) or inst_index < 0 or inst_index >= len(instructions):
                return jsonify({"error": "Ungültiger inst_index."}), 400

            # Merge only the provided fields — all other instructions stay untouched
            instructions[inst_index].update(patch)

            unique_id = uuid.uuid4().hex
            temp_filepath = f"{status_file_path}.{unique_id}.tmp"
            with open(temp_filepath, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                f.flush()
                os.fsync(f.fileno())
            os.replace(temp_filepath, status_file_path)

            return jsonify({"message": "Instruction gepatcht."}), 200

        except Exception as e:
            return jsonify({"error": f"Interner Fehler: {e}"}), 500


@app.route('/api/project_status/<path:project_id>/deviation', methods=['PATCH'])
@login_required
def api_patch_project_deviation(project_id):
    """Atomic patch for a single deviation field (e.g. ll, ll_note)."""
    payload = request.json
    if not payload:
        return jsonify({"error": "Kein Payload."}), 400

    step_id_str = payload.get('step_id_str')
    dev_index   = payload.get('dev_index')
    patch       = payload.get('patch')

    if step_id_str is None or dev_index is None or patch is None:
        return jsonify({"error": "Fehlende Felder."}), 400

    safe_project_id = re.sub(r'[^a-zA-Z0-9_-]', '', project_id)
    status_file_path = os.path.join('db', 'projectstatus', f'project_status_{safe_project_id}.json')

    with _get_file_lock(status_file_path):
        try:
            if not os.path.exists(status_file_path):
                return jsonify({"error": "Projektdatei nicht gefunden."}), 404

            with open(status_file_path, 'r', encoding='utf-8') as f:
                data = json.load(f)

            def find_step(steps, target_id):
                for s in steps:
                    if s.get('id_str') == target_id:
                        return s
                    found = find_step(s.get('children', []), target_id)
                    if found:
                        return found
                return None

            step = find_step(data.get('steps', []), step_id_str)
            if step is None:
                return jsonify({"error": f"Step '{step_id_str}' nicht gefunden."}), 404

            devs = step.get('deviations', [])
            if not isinstance(dev_index, int) or dev_index < 0 or dev_index >= len(devs):
                return jsonify({"error": "Ungültiger dev_index."}), 400

            allowed_fields = {'ll_status', 'll_send_status', 'll', 'll_note', 'll_cause', 'll_measure', 'll_recommendation', 'll_category'}
            devs[dev_index].update({k: v for k, v in patch.items() if k in allowed_fields})

            temp_filepath = f"{status_file_path}.{uuid.uuid4().hex}.tmp"
            with open(temp_filepath, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                f.flush()
                os.fsync(f.fileno())
            os.replace(temp_filepath, status_file_path)

            return jsonify({"message": "Deviation gepatcht."}), 200

        except Exception as e:
            return jsonify({"error": f"Interner Fehler: {e}"}), 500


def _find_project_filepath(safe_uuid):
    """Returns the absolute path to a project JSON file by UUID, or None."""
    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for d in search_dirs:
        if not os.path.exists(d):
            continue
        for filename in os.listdir(d):
            if not filename.endswith('.json'):
                continue
            filepath = os.path.join(d, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if data.get('uuid') == safe_uuid:
                    return filepath
            except Exception:
                continue
    return None


@app.route('/api/project/<string:project_uuid>/slave_units', methods=['POST'])
@login_required
def api_upload_slave_units(project_uuid):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    if 'file' not in request.files:
        return jsonify({"error": "Keine Datei hochgeladen."}), 400
    file = request.files['file']
    if not file.filename.lower().endswith('.csv'):
        return jsonify({"error": "Nur CSV-Dateien erlaubt."}), 400
    try:
        content = file.read().decode('utf-8-sig')
        reader = csv.DictReader(io.StringIO(content))
        slave_units = []
        for row in reader:
            norm = {k.strip().upper(): (v or '').strip() for k, v in row.items()}
            slave_units.append({
                'name': norm.get('NAME', ''),
                'pn':   norm.get('PN', ''),
                'sn':   norm.get('SN', ''),
            })
    except Exception as e:
        return jsonify({"error": f"CSV-Parsing Fehler: {e}"}), 400

    filepath = _find_project_filepath(safe_uuid)
    if not filepath:
        return jsonify({"error": "Projekt nicht gefunden."}), 404

    with _get_file_lock(filepath):
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
            data['slave_units'] = slave_units
            tmp = f"{filepath}.{uuid.uuid4().hex}.tmp"
            with open(tmp, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                f.flush(); os.fsync(f.fileno())
            os.replace(tmp, filepath)
            return jsonify({"message": f"{len(slave_units)} Slave Units gespeichert.", "count": len(slave_units)}), 200
        except Exception as e:
            return jsonify({"error": f"Speicherfehler: {e}"}), 500


@app.route('/api/project/<string:project_uuid>/slave_units', methods=['DELETE'])
@login_required
def api_delete_slave_units(project_uuid):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    filepath = _find_project_filepath(safe_uuid)
    if not filepath:
        return jsonify({"error": "Projekt nicht gefunden."}), 404
    with _get_file_lock(filepath):
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
            data.pop('slave_units', None)
            tmp = f"{filepath}.{uuid.uuid4().hex}.tmp"
            with open(tmp, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                f.flush(); os.fsync(f.fileno())
            os.replace(tmp, filepath)
            return jsonify({"message": "Slave Units entfernt."}), 200
        except Exception as e:
            return jsonify({"error": f"Fehler: {e}"}), 500


@app.route('/api/project/<string:project_uuid>/slave_units/add', methods=['POST'])
@login_required
def api_add_slave_unit(project_uuid):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    payload = request.json or {}
    name = (payload.get('name') or '').strip()
    pn   = (payload.get('pn')   or '').strip()
    sn   = (payload.get('sn')   or '').strip()
    if not name and not pn and not sn:
        return jsonify({"error": "Mindestens ein Feld (NAME, PN oder SN) muss ausgefüllt sein."}), 400
    filepath = _find_project_filepath(safe_uuid)
    if not filepath:
        return jsonify({"error": "Projekt nicht gefunden."}), 404
    with _get_file_lock(filepath):
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
            data.setdefault('slave_units', []).append({'name': name, 'pn': pn, 'sn': sn})
            tmp = f"{filepath}.{uuid.uuid4().hex}.tmp"
            with open(tmp, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                f.flush(); os.fsync(f.fileno())
            os.replace(tmp, filepath)
            return jsonify({"message": "Slave Unit hinzugefügt.", "count": len(data['slave_units'])}), 200
        except Exception as e:
            return jsonify({"error": f"Fehler: {e}"}), 500


@app.route('/api/project/<string:project_uuid>/slave_units/<int:index>', methods=['DELETE'])
@login_required
def api_delete_slave_unit(project_uuid, index):
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    filepath = _find_project_filepath(safe_uuid)
    if not filepath:
        return jsonify({"error": "Projekt nicht gefunden."}), 404
    with _get_file_lock(filepath):
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
            units = data.get('slave_units', [])
            if index < 0 or index >= len(units):
                return jsonify({"error": "Index außerhalb des Bereichs."}), 400
            units.pop(index)
            data['slave_units'] = units
            tmp = f"{filepath}.{uuid.uuid4().hex}.tmp"
            with open(tmp, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                f.flush(); os.fsync(f.fileno())
            os.replace(tmp, filepath)
            return jsonify({"message": "Slave Unit entfernt.", "count": len(units)}), 200
        except Exception as e:
            return jsonify({"error": f"Fehler: {e}"}), 500


@app.route('/timeline/view/<string:timeline_id>')
@login_required
def serve_timeline_page(timeline_id):
    config = load_app_config()
    timelines = config.get('timelines', [])
    timeline_config = next((tl for tl in config.get('timelines', []) if tl['id'] == timeline_id), None)
    if not timeline_config:
        abort(404)

    timeline_name_for_display = timeline_config.get('name', 'Timeline Ansicht')
    user_initials = "N/A"  # NEU
    vorname = session.get('user_vorname', '')  # NEU
    nachname = session.get('user_nachname', '')  # NEU
    if vorname and nachname: user_initials = (vorname[0] + nachname[0]).upper()  # NEU

    return render_template_string(
        TIMELINE_HTML_CONTENT,
        current_user_display_name=get_current_username_from_session(),
        url_for=url_for,
        session=session,
        timeline_id=timeline_id,
        timelines=timelines,
        current_user_initials=user_initials,
        active_page='timelines',
        timeline_name=timeline_name_for_display
    )


@app.route('/project/<string:project_uuid>')
@login_required
def serve_project_detail_page(project_uuid):
    """Zeigt die dedizierte Projektdetail-Seite für ein Projekt anhand seiner UUID."""
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    config = load_app_config()
    timelines = config.get('timelines', [])
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    initials = (vorname[0] + nachname[0]).upper() if vorname and nachname else 'N/A'
    back_url = url_for('dashboard_page')
    try:
        for search_dir in [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]:
            if not os.path.exists(search_dir):
                continue
            for fname in os.listdir(search_dir):
                if not fname.endswith('.json'):
                    continue
                with open(os.path.join(search_dir, fname), 'r', encoding='utf-8') as f:
                    d = json.load(f)
                if d.get('uuid') == safe_uuid:
                    tl_id = d.get('origin_timeline')
                    if tl_id:
                        back_url = url_for('serve_timeline_page', timeline_id=tl_id)
                    raise StopIteration
    except StopIteration:
        pass
    except Exception:
        pass
    return render_template_string(
        PROJECT_DETAIL_PAGE_HTML,
        project_uuid=safe_uuid,
        back_url=back_url,
        current_user=get_current_username_from_session(),
        current_user_display_name=get_current_username_from_session(),
        current_user_initials=initials,
        timelines=timelines,
        active_page='timelines',
        url_for=url_for,
        session=session
    )


def _collect_report_data(project_data):
    """Walks project steps, collects parts/deviations, and builds a hierarchical step list."""
    all_steps = project_data.get('steps', [])
    hw_data = load_test_equipment_data() or {}
    inventory = hw_data.get('inventory', {})

    hw_id_map = {}
    for part_number, item in inventory.items():
        item_name = item.get('name') or item.get('bezeichnung') or ''
        for serial in item.get('serials', []):
            hw_id = serial.get('id')
            if hw_id:
                hw_id_map[hw_id] = {'name': item_name, 'pn': part_number, 'sn': serial.get('sn', '')}

    def resolve_label(selected_option, selected_label=None):
        if selected_label:
            return selected_label
        if not selected_option:
            return None
        if selected_option in hw_id_map:
            e = hw_id_map[selected_option]
            return e['name'] or selected_option
        item = inventory.get(selected_option)
        if item:
            return item.get('name') or item.get('bezeichnung') or selected_option
        return selected_option

    _ref_re   = re.compile(r'\[\[REF:(.*?)\]\]')
    _img_re   = re.compile(r'\[\[IMAGE:[^|]*\|?(.*?)\]\]')
    _tool_re  = re.compile(r'\((\d+)\)')

    def clean_inst_text(text):
        if not text:
            return text
        text = _ref_re.sub(r'\1', text)
        text = _img_re.sub(lambda m: m.group(1) if m.group(1) else '', text)
        text = _tool_re.sub(r'(\1)', text)
        return text.strip()

    parts = []
    deviations = []
    step_stats = {}

    skip_types = {'deviation', 'troubleshooting', 'solution'}

    for step in all_steps:
        sid = step.get('id_str', '')
        step_name = step.get('name', '—')
        step_stats[sid] = {'parts': [], 'deviations': []}

        for inst in step.get('instructions', []):
            if inst.get('type') == 'part_selection':
                hw_entry = hw_id_map.get(inst.get('selected_option', ''), {})
                if hw_entry:
                    part_label = hw_entry.get('name') or resolve_label(inst.get('selected_option'), inst.get('selected_label'))
                else:
                    part_label = resolve_label(inst.get('selected_option'), inst.get('selected_label'))
                part = {
                    'step_id': sid,
                    'step_name': step_name,
                    'cycle_number': step.get('cycle_number', 1),
                    'text': clean_inst_text(inst.get('text', '')),
                    'part_label': part_label,
                    'pn': hw_entry.get('pn', ''),
                    'sn': hw_entry.get('sn', ''),
                    'completed_by': inst.get('completed_by'),
                    'completed_at': inst.get('completed_at'),
                    'checked': bool(inst.get('checked')),
                    'nr': bool(inst.get('nr')),
                }
                parts.append(part)
                step_stats[sid]['parts'].append(part)

        for dev_idx, dev in enumerate(step.get('deviations', [])):
            deviation = {
                'step_id': sid,
                'step_name': step_name,
                'cycle_number': step.get('cycle_number', 1),
                'dev_index': dev_idx,
                'dev_id': dev.get('id', ''),
                'reason': dev.get('reason', ''),
                'details': dev.get('details', ''),
                'status': dev.get('status', 'Open'),
                'hub_incident_number': dev.get('hub_incident_nr') or dev.get('hub_incident_number', ''),
                'hub_incident_type': dev.get('hub_incident_type', ''),
                'reported_by': dev.get('reported_by', ''),
                'reported_at': dev.get('reported_at', ''),
                'thread': dev.get('thread', []),
                'll_status': dev.get('ll_status', ''),
                'll_send_status': dev.get('ll_send_status', ''),
                'll': bool(dev.get('ll', False)),
                'll_note': dev.get('ll_note', ''),
                'll_cause': dev.get('ll_cause', ''),
                'll_measure': dev.get('ll_measure', ''),
                'll_recommendation': dev.get('ll_recommendation', ''),
                'll_category': dev.get('ll_category', ''),
            }
            deviations.append(deviation)
            step_stats[sid]['deviations'].append(deviation)

    def make_step_node(step):
        sid = step.get('id_str', '')
        stats = step_stats.get(sid, {'parts': [], 'deviations': []})
        return {
            'id_str': sid,
            'name': step.get('name', '—'),
            'status': step.get('status', ''),
            'cycle_number': step.get('cycle_number', 1),
            'parts_count': sum(1 for p in stats['parts'] if not p['nr']),
            'checked_parts_count': sum(1 for p in stats['parts'] if p['checked'] and not p['nr']),
            'deviations_count': len(stats['deviations']),
            'open_deviations_count': sum(1 for d in stats['deviations'] if d['status'] == 'Open'),
        }

    top_level = [s for s in all_steps
                 if not s.get('parent_id_str')
                 and s.get('type', 'main') not in skip_types]

    report_steps = []
    for step in top_level:
        sid = step.get('id_str', '')
        node = make_step_node(step)
        node['children'] = [
            make_step_node(c) for c in all_steps
            if c.get('parent_id_str') == sid
            and c.get('type', 'main') not in skip_types
        ]
        report_steps.append(node)

    max_cycle = max((s['cycle_number'] for s in report_steps), default=1) if report_steps else 1
    return parts, deviations, report_steps, max_cycle


def _collect_protocol_data(project_data):
    """Build hierarchical step+instruction list for the print protocol."""
    _ref_re  = re.compile(r'\[\[REF:(.*?)\]\]')
    _img_re  = re.compile(r'\[\[IMAGE:[^|]*\|?(.*?)\]\]')
    _tool_re = re.compile(r'\((\d+)\)')

    def clean_text(text):
        if not text:
            return ''
        text = _ref_re.sub(r'\1', text)
        text = _img_re.sub(lambda m: m.group(1) if m.group(1) else '', text)
        text = _tool_re.sub(r'(\1)', text)
        return text.strip()

    hw_data = load_test_equipment_data() or {}
    inventory = hw_data.get('inventory', {})
    hw_id_map = {}
    for pn, item in inventory.items():
        item_name = item.get('name') or item.get('bezeichnung') or ''
        for serial in item.get('serials', []):
            hw_id = serial.get('id')
            if hw_id:
                hw_id_map[hw_id] = {'name': item_name, 'pn': pn, 'sn': serial.get('sn', '')}

    def resolve_hw(selected_option, selected_label=None):
        if selected_option in hw_id_map:
            e = hw_id_map[selected_option]
            parts = [e['name'] or selected_option]
            if e['pn']:
                parts.append(f"PN {e['pn']}")
            if e['sn']:
                parts.append(f"SN {e['sn']}")
            return ' | '.join(parts)
        item = inventory.get(selected_option)
        if item:
            return item.get('name') or item.get('bezeichnung') or selected_option
        return selected_label or selected_option or ''

    skip_types = {'deviation', 'troubleshooting', 'solution'}

    all_steps = project_data.get('steps', [])

    def build_inst_list(step):
        result = []
        for inst in step.get('instructions', []):
            itype = inst.get('type', 'normal')
            if itype == 'part_selection':
                detail = resolve_hw(inst.get('selected_option', ''), inst.get('selected_label'))
            elif itype == 'text_input':
                detail = inst.get('text_value', '')
            else:
                detail = ''
            result.append({
                'type':         itype,
                'step_num':     inst.get('step_num', ''),
                'text_clean':   clean_text(inst.get('text', '')),
                'detail':       detail,
                'checked':      bool(inst.get('checked')),
                'nr':           bool(inst.get('nr')),
                'completed_by': inst.get('completed_by'),
                'completed_at': inst.get('completed_at'),
            })
        return result

    def build_node(step):
        sid = step.get('id_str', '')
        children = [
            {**build_node(c), 'instructions': build_inst_list(c)}
            for c in all_steps
            if c.get('parent_id_str') == sid and c.get('type', 'main') not in skip_types
        ]
        return {
            'id_str':       sid,
            'name':         step.get('name', '—'),
            'status':       step.get('status', ''),
            'cycle_number': step.get('cycle_number'),
            'completed_by': step.get('completed_by'),
            'completed_at': step.get('completed_at'),
            'instructions': build_inst_list(step),
            'children':     children,
        }

    top_level = [
        build_node(s) for s in all_steps
        if not s.get('parent_id_str') and s.get('type', 'main') not in skip_types
    ]
    return top_level


@app.route('/project/<string:project_uuid>/report')
@login_required
def serve_engineer_report(project_uuid):
    """Engineer Report: used parts, deviations, and placeholders for future integrations."""
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)

    project_data = None
    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        for filename in os.listdir(search_dir):
            if not filename.endswith('.json'):
                continue
            filepath = os.path.join(search_dir, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if data.get('uuid') == safe_uuid:
                    project_data = data
                    break
            except Exception:
                continue
        if project_data:
            break

    if not project_data:
        abort(404)

    parts, deviations, report_steps, max_cycle = _collect_report_data(project_data)
    open_deviations = sum(1 for d in deviations if d['status'] == 'Open')
    slave_units = project_data.get('slave_units', [])

    config = load_app_config()
    timelines = config.get('timelines', [])
    tl_id = project_data.get('origin_timeline')
    back_url = url_for('serve_timeline_page', timeline_id=tl_id) if tl_id else url_for('dashboard_page')

    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    initials = (vorname[0] + nachname[0]).upper() if vorname and nachname else 'N/A'

    from datetime import datetime as _dt
    report_date = _dt.now().strftime('%d.%m.%Y %H:%M')

    return render_template_string(
        ENGINEER_REPORT_HTML,
        project_uuid=safe_uuid,
        project=project_data,
        parts=parts,
        deviations=deviations,
        report_steps=report_steps,
        open_deviations=open_deviations,
        max_cycle=max_cycle,
        slave_units=slave_units,
        parts_json=json.dumps(parts, ensure_ascii=False),
        deviations_json=json.dumps(deviations, ensure_ascii=False),
        report_date=report_date,
        back_url=back_url,
        timelines=timelines,
        current_user_initials=initials,
        current_user_display_name=get_current_username_from_session(),
        active_page='timelines',
        url_for=url_for,
        session=session,
    )


@app.route('/project/<string:project_uuid>/protocol')
@login_required
def serve_project_protocol(project_uuid):
    """Print-friendly step+instruction protocol for a project."""
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)

    project_data = None
    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        for filename in os.listdir(search_dir):
            if not filename.endswith('.json'):
                continue
            filepath = os.path.join(search_dir, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as pf:
                    data = json.load(pf)
                if data.get('uuid') == safe_uuid:
                    project_data = data
                    break
            except Exception:
                continue
        if project_data:
            break

    if not project_data:
        abort(404)

    protocol_steps = _collect_protocol_data(project_data)
    _, deviations, _, _ = _collect_report_data(project_data)

    from datetime import datetime as _dt
    report_date = _dt.now().strftime('%d.%m.%Y %H:%M')

    return render_template_string(
        PROJECT_PROTOCOL_HTML,
        project_uuid=safe_uuid,
        project=project_data,
        protocol_steps=protocol_steps,
        deviations=deviations,
        report_date=report_date,
        url_for=url_for,
        session=session,
    )


@app.route('/api/project_by_uuid/<string:project_uuid>')
@login_required
def api_get_project_by_uuid(project_uuid):
    """Lädt ein Projekt anhand seiner UUID aus dem projectstatus-Verzeichnis (inkl. archiv/)."""
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    if not os.path.exists(PROJECT_STATUS_DIR):
        return jsonify({"error": "Kein Projektverzeichnis gefunden."}), 404
    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        for filename in os.listdir(search_dir):
            if not filename.endswith('.json'):
                continue
            filepath = os.path.join(search_dir, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
            except Exception:
                continue
            if data.get('uuid') == safe_uuid:
                if 'name' not in data:
                    data['name'] = f"{data.get('project_code','?')} / SN {data.get('engine_serial','?')}"
                # Migration: cycle_number und global_step_number für alte Projekte ergänzen
                if not data.get('_cycle_model_v1'):
                    changed = _migrate_steps_to_cycle_model(data)
                    if changed:
                        save_json_file(filepath, data)
                # Instructions und has_document aus Template einmergen
                templates = load_process_templates()
                template = find_best_template_for_engine(data.get('engine_type'), templates)
                tmpl_map = {s['name']: s for s in template.get('steps', [])}
                for step in data.get('steps', []):
                    tmpl = tmpl_map.get(step.get('name', ''))
                    if tmpl:
                        if not step.get('instructions'):
                            step['instructions'] = tmpl.get('instructions', [])
                        if not step.get('tools'):
                            step['tools'] = tmpl.get('tools', [])
                        if not step.get('references'):
                            step['references'] = tmpl.get('references', [])
                        step['has_document'] = bool(tmpl.get('document_template'))
                data['laufkarten_template'] = template.get('laufkarten', [])
                return jsonify(data)
    return jsonify({"error": f"Projekt mit UUID '{safe_uuid}' nicht gefunden."}), 404


def _migrate_steps_to_cycle_model(data: dict) -> bool:
    """Ergänzt cycle_number und global_step_number für Projekte ohne Zyklus-Modell. Gibt True zurück wenn Änderungen gemacht wurden."""
    steps = data.get('steps', [])
    if not steps:
        data['_cycle_model_v1'] = True
        return True
    if all('cycle_number' in s for s in steps):
        data['_cycle_model_v1'] = True
        return True
    main_steps = sorted([s for s in steps if not s.get('parent_id_str')],
                        key=lambda s: [int(p) for p in s['id_str'].split('.') if p.isdigit()] or [0])
    counter = 1
    for ms in main_steps:
        ms['cycle_number'] = 1
        ms['global_step_number'] = counter
        counter += 1
        children = sorted([s for s in steps if s.get('parent_id_str') == ms['id_str']],
                          key=lambda s: [int(p) for p in s['id_str'].split('.') if p.isdigit()] or [0])
        for child in children:
            child['cycle_number'] = 1
            child['global_step_number'] = counter
            counter += 1
    data['_cycle_model_v1'] = True
    return True


@app.route('/api/project_by_uuid/<string:project_uuid>/laufkarten', methods=['PATCH'])
@login_required
def api_patch_laufkarte_number(project_uuid):
    """Speichert eine Laufkarten-Nummer für einen bestimmten Zyklus im Projekt-JSON."""
    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    body = request.json or {}
    cycle_key = str(body.get('cycle', ''))
    lk_id = str(body.get('lk_id', ''))
    number = str(body.get('number', ''))
    if not cycle_key or not lk_id:
        return jsonify({"error": "cycle und lk_id sind erforderlich."}), 400

    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        for filename in os.listdir(search_dir):
            if not filename.endswith('.json'):
                continue
            filepath = os.path.join(search_dir, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as fh:
                    data = json.load(fh)
            except Exception:
                continue
            if data.get('uuid') == safe_uuid:
                lk_data = data.setdefault('laufkarten', {})
                lk_data.setdefault(cycle_key, {})[lk_id] = number
                if save_json_file(filepath, data):
                    return jsonify({"ok": True}), 200
                return jsonify({"error": "Fehler beim Speichern."}), 500
    return jsonify({"error": "Projekt nicht gefunden."}), 404


@app.route('/api/project_by_uuid/<string:project_uuid>/new_cycle', methods=['POST'])
@login_required
def api_create_new_cycle(project_uuid):
    """Erstellt einen neuen Zyklus: setzt offene Schritte auf N/R, kopiert gewählte Status."""
    user_roles = session.get('roles', [])
    allowed_roles = {Roles.ADMIN, Roles.PLANNER, Roles.TESTMECHANIC, Roles.TESTCELLINSPECTOR,
                     Roles.TESTENGINEER, Roles.MAINTENANCEMANAGER, Roles.HARDWAREMANAGER}
    if not any(r in user_roles for r in allowed_roles):
        return jsonify({"error": "Keine Berechtigung."}), 403

    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)
    body = request.json or {}
    copy_steps = set(body.get('copy_steps', []))

    filepath = None
    data = None
    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        for fname in os.listdir(search_dir):
            if not fname.endswith('.json'):
                continue
            fp = os.path.join(search_dir, fname)
            try:
                with open(fp, 'r', encoding='utf-8') as fh:
                    d = json.load(fh)
                if d.get('uuid') == safe_uuid:
                    filepath, data = fp, d
                    break
            except Exception:
                continue
        if data:
            break

    if not data:
        return jsonify({"error": "Projekt nicht gefunden."}), 404

    if not data.get('_cycle_model_v1'):
        _migrate_steps_to_cycle_model(data)

    steps = data['steps']
    max_cycle = max((s.get('cycle_number') or 1 for s in steps), default=1)
    new_cycle = max_cycle + 1

    timestamp = datetime.now().strftime("%d.%m.%Y, %H:%M")
    user_display = f"{session.get('user_vorname', '')} {session.get('user_nachname', '')}".strip() or get_current_username_from_session()

    # Nur "Upcoming"-Schritte im aktuellen Zyklus auf N/R setzen — alle anderen Status bleiben
    for s in steps:
        if (s.get('cycle_number') or 1) == max_cycle and s.get('status') == 'Upcoming':
            s['status'] = 'N/R'
            s['completed_by'] = user_display
            s['completed_at'] = timestamp
            s.setdefault('history', []).append({'action': f'N/R gesetzt beim Start von Zyklus {new_cycle}', 'user': user_display, 'timestamp': timestamp})

    # Template-Struktur aus aktuellem Zyklus übernehmen (nur Hauptschritte, sortiert)
    current_main = sorted(
        [s for s in steps if not s.get('parent_id_str') and (s.get('cycle_number') or 1) == max_cycle],
        key=lambda s: s.get('global_step_number') or 0
    )
    max_global = max((s.get('global_step_number') or 0 for s in steps), default=0)

    for ms in current_main:
        parent_copied = ms['id_str'] in copy_steps
        max_global += 1
        new_status = ms['status'] if parent_copied else 'Upcoming'
        new_id = str(max_global)
        new_main = {
            'id_str': new_id,
            'parent_id_str': None,
            'name': ms['name'],
            'type': ms.get('type', 'main'),
            'status': new_status,
            'cycle_number': new_cycle,
            'global_step_number': max_global,
            'copied_from_global': ms['id_str'] if parent_copied else None,
            'status_detail': '',
            'notes': '',
            'is_expanded': True,
            'deviations': [],
            'tools': ms.get('tools', []),
            'references': ms.get('references', []),
            'has_document': ms.get('has_document', False),
            'last_updated_by': None,
            'last_updated_at': None,
            'history': []
        }
        if parent_copied:
            new_main['last_updated_by'] = user_display
            new_main['last_updated_at'] = timestamp
            new_main['history'].append({'action': f'Status übernommen von {ms["id_str"]} (Zyklus {max_cycle})', 'user': user_display, 'timestamp': timestamp})
        steps.append(new_main)

        # Kinder-Schritte aus aktuellem Zyklus ebenfalls klonen
        current_children = sorted(
            [s for s in steps if s.get('parent_id_str') == ms['id_str'] and (s.get('cycle_number') or 1) == max_cycle],
            key=lambda s: s.get('global_step_number') or 0
        )
        for child in current_children:
            max_global += 1
            child_status = child['status'] if parent_copied else 'Upcoming'
            new_child = {
                'id_str': f"{new_id}.{max_global}",
                'parent_id_str': new_id,
                'name': child['name'],
                'type': child.get('type', 'main'),
                'status': child_status,
                'cycle_number': new_cycle,
                'global_step_number': max_global,
                'copied_from_global': child['id_str'] if parent_copied else None,
                'status_detail': '',
                'notes': '',
                'is_expanded': False,
                'deviations': [],
                'tools': child.get('tools', []),
                'references': child.get('references', []),
                'has_document': child.get('has_document', False),
                'last_updated_by': user_display if parent_copied else None,
                'last_updated_at': timestamp if parent_copied else None,
                'history': [{'action': f'Status übernommen von {child["id_str"]} (Zyklus {max_cycle})', 'user': user_display, 'timestamp': timestamp}] if parent_copied else []
            }
            steps.append(new_child)

    # Fortschritt neu berechnen (nur aktueller Zyklus)
    current_steps = [s for s in steps if (s.get('cycle_number') or 1) == new_cycle]
    relevant = [s for s in current_steps if s.get('status') != 'N/R']
    if relevant:
        completed = sum(1 for s in relevant if s['status'] == 'Completed')
        data['progress_percent'] = round(completed / len(relevant) * 100)
    else:
        data['progress_percent'] = 0

    if save_json_file(filepath, data):
        return jsonify(data), 200
    return jsonify({"error": "Speichern fehlgeschlagen."}), 500


@app.route('/api/project_by_uuid/<string:project_uuid>/delete_cycle', methods=['DELETE'])
@login_required
def api_delete_latest_cycle(project_uuid):
    """Löscht den neuesten Zyklus, aber nur wenn noch kein Schritt darin manuell bearbeitet wurde."""
    user_roles = session.get('roles', [])
    allowed_roles = {Roles.ADMIN, Roles.PLANNER, Roles.TESTMECHANIC, Roles.TESTCELLINSPECTOR,
                     Roles.TESTENGINEER, Roles.MAINTENANCEMANAGER, Roles.HARDWAREMANAGER}
    if not any(r in user_roles for r in allowed_roles):
        return jsonify({"error": "Keine Berechtigung."}), 403

    safe_uuid = re.sub(r'[^a-zA-Z0-9_-]', '', project_uuid)

    filepath = None
    data = None
    search_dirs = [PROJECT_STATUS_DIR, os.path.join(PROJECT_STATUS_DIR, 'archiv')]
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        for fname in os.listdir(search_dir):
            if not fname.endswith('.json'):
                continue
            fp = os.path.join(search_dir, fname)
            try:
                with open(fp, 'r', encoding='utf-8') as fh:
                    d = json.load(fh)
                if d.get('uuid') == safe_uuid:
                    filepath, data = fp, d
                    break
            except Exception:
                continue
        if data:
            break

    if not data:
        return jsonify({"error": "Projekt nicht gefunden."}), 404

    steps = data.get('steps', [])
    if not steps:
        return jsonify({"error": "Keine Schritte vorhanden."}), 400

    max_cycle = max((s.get('cycle_number') or 1 for s in steps), default=1)
    if max_cycle <= 1:
        return jsonify({"error": "Erster Zyklus kann nicht gelöscht werden."}), 400

    # Prüfen ob irgendeiner der neu erstellten (nicht kopierten) Schritte bereits bearbeitet wurde
    new_steps = [s for s in steps if (s.get('cycle_number') or 1) == max_cycle and not s.get('copied_from_global')]
    modified = [s for s in new_steps if s.get('status', 'Upcoming') != 'Upcoming']
    if modified:
        names = ', '.join(s.get('name', s.get('id_str', '?')) for s in modified[:3])
        return jsonify({"error": f"Zyklus {max_cycle} kann nicht gelöscht werden — folgende Schritte wurden bereits bearbeitet: {names}"}), 409

    # Schritte im Vorgänger-Zyklus wiederherstellen, die beim Erstellen auf N/R gesetzt wurden
    restore_marker = f'N/R gesetzt beim Start von Zyklus {max_cycle}'
    for s in steps:
        if (s.get('cycle_number') or 1) != max_cycle - 1:
            continue
        history = s.get('history') or []
        if any(h.get('action') == restore_marker for h in history):
            s['status'] = 'Upcoming'
            s['completed_by'] = None
            s['completed_at'] = None
            s['history'] = [h for h in history if h.get('action') != restore_marker]

    # Alle Schritte des neuesten Zyklus entfernen
    data['steps'] = [s for s in steps if (s.get('cycle_number') or 1) != max_cycle]

    # Laufkarten des gelöschten Zyklus entfernen
    if 'laufkarten' in data:
        data['laufkarten'].pop(str(max_cycle), None)

    # Fortschritt neu berechnen (jetzt wieder auf Vorgänger-Zyklus)
    remaining = data['steps']
    prev_cycle = max_cycle - 1
    current_steps = [s for s in remaining if (s.get('cycle_number') or 1) == prev_cycle]
    relevant = [s for s in current_steps if s.get('status') != 'N/R']
    if relevant:
        completed = sum(1 for s in relevant if s['status'] == 'Completed')
        data['progress_percent'] = round(completed / len(relevant) * 100)
    else:
        data['progress_percent'] = 0

    if save_json_file(filepath, data):
        return jsonify(data), 200
    return jsonify({"error": "Speichern fehlgeschlagen."}), 500


@app.route('/my_qualifications')
@login_required
def serve_my_qualifications_page():
    config = load_app_config()
    timelines = config.get('timelines', [])
    display_username_for_header = get_current_username_from_session()
    user_full_name_for_page_body = get_current_user_full_name_from_session()

    user_level_from_csv = "Unbekannt"
    current_qualifications_data = []
    ongoing_trainings_data = []
    error_message_personal_qualis = None

    user_id_for_data = session.get('user_id')

    if user_id_for_data:
        user_object = next((u for u in ALL_USERS if u['id'] == user_id_for_data), None)
        if user_object:
            user_level_from_csv = user_object.get('level', 'Standardbenutzer')

        user_specific_qual_entry = next((q for q in ALL_QUALIFICATIONS if q.get('id') == user_id_for_data), None)

        if user_specific_qual_entry:
            for merkmal_info in user_specific_qual_entry.get('merkmale', []):
                qual_name = merkmal_info.get('merkmal', 'Unbekannt')
                qual_wert = merkmal_info.get('wert', '').upper()

                start_date = merkmal_info.get('start_date', 'N/A')
                end_date = merkmal_info.get('end_date', 'N/A')
                cert_path = merkmal_info.get('cert_path', '')

                if qual_wert == 'X':
                    current_qualifications_data.append(
                        {"name": qual_name, "end_date": end_date, "cert_path": cert_path})
                elif qual_wert in ['TS', 'TP', 'TT']:
                    progress_percent = 0
                    if qual_wert == 'TS':
                        progress_percent = 50
                    elif qual_wert == 'TP':
                        progress_percent = 10
                    elif qual_wert == 'TT':
                        progress_percent = 75
                    ongoing_trainings_data.append(
                        {"name": qual_name, "start_date": start_date, "progress": progress_percent,
                         "status": qual_wert})
        else:
            error_message_personal_qualis = f"Kein Qualifikationseintrag für '{user_full_name_for_page_body}' (ID: {user_id_for_data}) gefunden."
    else:
        error_message_personal_qualis = "Benutzer nicht eingeloggt."

    current_qualifications_data.sort(key=lambda x: x['name'])
    ongoing_trainings_data.sort(key=lambda x: x['name'])

    # Zukünftige Abwesenheiten aus Anwesenheitsplanung
    future_absences = []
    vacation_quota_info = None
    if user_id_for_data:
        _event_labels = {'U': 'Urlaub', 'K': 'Krank', 'G': 'Gleittag', 'Xs': 'Sonderurlaub', 'D': 'Dienstreise', 'TZ': 'Teilzeit'}
        _event_colors = {'U': 'green', 'K': 'red', 'G': 'blue', 'Xs': 'purple', 'D': 'orange', 'TZ': 'yellow'}
        _today = date.today()
        _year = str(_today.year)
        emp_att = next((e for e in ALL_EMPLOYEES if e.get('id') == user_id_for_data), None)
        if emp_att:
            # Alle Abwesenheitstage des aktuellen Jahres sammeln (vergangen + zukünftig)
            _all_year_days = []
            _absence_days = []
            for week in emp_att.get('weeks', []):
                for day in week.get('days', []):
                    d = day.get('date')
                    ev = (day.get('ereignis') or '').strip()
                    if not (ev and isinstance(d, date)):
                        continue
                    if d.year == _today.year:
                        _all_year_days.append((d, ev))
                    if d >= _today:
                        _absence_days.append((d, ev))

            # Urlaubsquote
            _quotas = load_vacation_quotas()
            _quota = int((_quotas.get(user_id_for_data) or {}).get(_year, 30))
            _used_u = sum(1 for d, ev in _all_year_days if ev == 'U' and d < _today)
            _planned_u = sum(1 for d, ev in _absence_days if ev == 'U')
            vacation_quota_info = {
                'quota': _quota,
                'used': _used_u,
                'planned': _planned_u,
                'remaining': _quota - _used_u - _planned_u,
                'year': _year
            }

            # Zukünftige Blöcke gruppieren (mit Tageanzahl)
            _absence_days.sort(key=lambda x: x[0])
            if _absence_days:
                rs, rev = _absence_days[0]
                re_ = rs
                _block_days = [rs]
                for d, ev in _absence_days[1:]:
                    if ev == rev and (d - re_).days <= 4:
                        re_ = d
                        _block_days.append(d)
                    else:
                        future_absences.append({
                            'start': rs.strftime('%d.%m.%Y'),
                            'end': re_.strftime('%d.%m.%Y'),
                            'label': _event_labels.get(rev, rev),
                            'code': rev,
                            'color': _event_colors.get(rev, 'slate'),
                            'single': rs == re_,
                            'days': len(_block_days)
                        })
                        rs, re_, rev = d, d, ev
                        _block_days = [d]
                future_absences.append({
                    'start': rs.strftime('%d.%m.%Y'),
                    'end': re_.strftime('%d.%m.%Y'),
                    'label': _event_labels.get(rev, rev),
                    'code': rev,
                    'color': _event_colors.get(rev, 'slate'),
                    'single': rs == re_,
                    'days': len(_block_days)
                })

    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname: user_initials = (vorname[0] + nachname[0]).upper()

    return render_template_string(
        PERSONAL_QUALIFICATIONS_HTML_CONTENT,
        user_full_name=user_full_name_for_page_body,
        current_user_display_name=display_username_for_header,
        user_level=user_level_from_csv,
        current_qualifications=current_qualifications_data,
        ongoing_trainings=ongoing_trainings_data,
        future_absences=future_absences,
        vacation_quota_info=vacation_quota_info,
        error_message=error_message_personal_qualis,
        url_for=url_for,
        current_user_initials=user_initials,
        active_page='my_qualifications',
        session=session,
        user_id=user_id_for_data
    )


@app.route('/qualifications_table')
@login_required
def serve_qualifications_table_page():
    config = load_app_config()
    timelines = config.get('timelines', [])
    grouped_qualifications_data, merkmale_for_table = load_and_prepare_qualifications_data()
    dashboard_data = get_employee_dashboard_data()
    if not ALL_USERS:
        load_all_users_on_startup()
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    return render_template_string(
        QUALIFICATIONS_TABLE_HTML_CONTENT,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        timelines=timelines,
        active_page='qualifications_table',
        grouped_qualifications=grouped_qualifications_data,
        merkmale_in_order=merkmale_for_table,
        employees_list=dashboard_data['employees_list'],
        groups_list=dashboard_data['groups_list'],
        skills_list=dashboard_data['skills_list'],
        full_employee_data=dashboard_data['full_employee_data'],
        full_qualification_data=dashboard_data['full_qualification_data'],
        users=ALL_USERS,
        url_for=url_for,
        session=session
    )


@app.route('/admin/user_management')
@admin_required
def user_management_page():
    user_name = get_current_username_from_session() or "Unbekannt"
    user_initials = "N/A"
    vorname = session.get('user_vorname', '')
    nachname = session.get('user_nachname', '')
    if vorname and nachname:
        user_initials = (vorname[0] + nachname[0]).upper()

    sorted_users = sorted(ALL_USERS, key=lambda u: u.get('nachname', '').lower())

    # Gruppe (group_id) kommt aus attendance_data.json — read-only hier
    att_data = load_raw_attendance_data()
    attendance_groups = {e['id']: e.get('group_id', '') for e in att_data.get('employees', []) if e.get('id')}

    return render_template_string(
        USER_MANAGEMENT_HTML_CONTENT,
        users=sorted_users,
        available_roles=AVAILABLE_ROLES,
        attendance_groups=attendance_groups,
        current_user_display_name=user_name,
        current_user_initials=user_initials,
        active_page='user_management',
        url_for=url_for,
        session=session
    )


@app.route('/api/admin/add_user', methods=['POST'])
@admin_required
def api_add_user():
    data = request.json
    if not all(k in data for k in ['nachname', 'vorname', 'id', 'level', 'roles']):
        return jsonify({"error": "Fehlende Benutzerdaten."}), 400

    new_user_id = data['id'].strip().lower()
    if any(u['id'] == new_user_id for u in ALL_USERS):
        return jsonify({"error": f"Benutzer-ID '{new_user_id.upper()}' existiert bereits."}), 409

    is_employee = data.get('is_employee', True)

    new_user_obj = {
        'nachname': data['nachname'].strip().upper(),
        'vorname': data['vorname'].strip().capitalize(),
        'id': new_user_id,
        'normalized_name': f"{data['nachname'].strip().lower()}, {data['vorname'].strip().lower()}",
        'level': data['level'].strip(),
        'password': 'MBET',
        'roles': data.get('roles', []),
        'units': data.get('units', []),
        'is_employee': is_employee,
    }
    ALL_USERS.append(new_user_obj)

    try:
        _save_users_csv()
        if is_employee:
            _provision_employee(new_user_obj)
        return jsonify({"message": "Benutzer erfolgreich hinzugefügt."}), 201
    except Exception as e:
        ALL_USERS.pop()
        print(f"Fehler Speichern Benutzer-CSV (Hinzufügen): {e}")
        traceback.print_exc()
        return jsonify({"error": "Fehler beim Speichern."}), 500

@app.route('/api/admin/users/<user_id>/edit', methods=['PUT'])
@admin_required
def api_edit_user(user_id):
    data = request.json
    print(f"DEBUG EDIT RECEIVED: {data}")
    if not data or not all(k in data for k in ['nachname', 'vorname', 'level', 'roles']):
        return jsonify({"error": "Fehlende oder ungültige Benutzerdaten."}), 400

    user_to_edit = next((user for user in ALL_USERS if user['id'] == user_id.lower()), None)
    if not user_to_edit:
        return jsonify({"error": "Benutzer nicht gefunden."}), 404

    original_values = user_to_edit.copy()
    was_employee = user_to_edit.get('is_employee', True)
    new_is_employee = data.get('is_employee', True)

    user_to_edit['nachname'] = data['nachname'].strip().upper()
    user_to_edit['vorname'] = data['vorname'].strip().capitalize()
    user_to_edit['level'] = data['level'].strip()
    user_to_edit['roles'] = data.get('roles', [])
    user_to_edit['units'] = data.get('units', [])
    user_to_edit['normalized_name'] = f"{data['nachname'].strip().lower()}, {data['vorname'].strip().lower()}"
    user_to_edit['is_employee'] = new_is_employee

    try:
        _save_users_csv()
        if new_is_employee and not was_employee:
            _provision_employee(user_to_edit)
        elif not new_is_employee and was_employee:
            _deprovision_employee(user_to_edit['id'])
        return jsonify({"message": "Benutzer erfolgreich aktualisiert."}), 200
    except Exception as e:
        user_index = ALL_USERS.index(user_to_edit)
        ALL_USERS[user_index] = original_values
        print(f"Fehler beim Speichern der Benutzer-CSV nach Bearbeitung: {e}")
        traceback.print_exc()
        return jsonify({"error": "Fehler beim Speichern der Änderungen."}), 500


@app.route('/api/admin/users/<user_id>', methods=['DELETE'])
@admin_required
def api_delete_user(user_id):
    user_to_delete_idx = -1
    original_user_data = None
    for idx, user in enumerate(ALL_USERS):
        if user['id'] == user_id.lower():
            user_to_delete_idx = idx
            original_user_data = user.copy()
            break
    if user_to_delete_idx == -1:
        return jsonify({"error": "Benutzer nicht gefunden."}), 404

    del ALL_USERS[user_to_delete_idx]

    try:
        _save_users_csv()
        # Attendance-Slot entfernen (Kaskade)
        _deprovision_employee(user_id.lower())
        # Quali-Eintrag archivieren: bleibt in CSV erhalten (historische Daten)
        return jsonify({"message": "Benutzer erfolgreich gelöscht. Anwesenheitsslot wurde entfernt, Qualifikationsdaten bleiben archiviert."}), 200
    except Exception as e:
        if original_user_data:
            ALL_USERS.insert(user_to_delete_idx, original_user_data)
        print(f"Fehler beim Speichern nach dem Löschen: {e}")
        return jsonify({"error": "Fehler beim Speichern nach dem Löschen."}), 500

@app.route('/api/update_qualification_status', methods=['POST'])
@login_required
def api_update_qualification_status():
    data = request.json
    if not data or not all(k in data for k in ['id', 'merkmal', 'new_value', 'start_date', 'end_date', 'cert_path']):
        return jsonify({"error": "Fehlende Daten (id, merkmal, status, daten...)."}), 400

    user_id = data['id'].strip().lower()
    merkmal_name_to_update = data['merkmal'].strip()
    new_status_value = data['new_value'].strip()
    new_start_date = data['start_date'].strip()
    new_end_date = data['end_date'].strip()
    new_cert_path = data['cert_path'].strip()

    global ALL_QUALIFICATIONS, MERKMALE_IN_ORDER

    found_employee = False
    updated_in_memory = False

    for qual_entry in ALL_QUALIFICATIONS:
        if qual_entry.get('id', '').strip().lower() == user_id:
            found_employee = True
            merkmal_found_for_employee = False
            if 'merkmale' not in qual_entry or not isinstance(qual_entry['merkmale'], list):
                qual_entry['merkmale'] = []

            for merkmal_data in qual_entry['merkmale']:
                if merkmal_data.get('merkmal', '').strip() == merkmal_name_to_update:
                    merkmal_data['wert'] = new_status_value
                    merkmal_data['start_date'] = new_start_date
                    merkmal_data['end_date'] = new_end_date
                    merkmal_data['cert_path'] = new_cert_path
                    merkmal_found_for_employee = True
                    updated_in_memory = True
                    break

            if not merkmal_found_for_employee and merkmal_name_to_update in MERKMALE_IN_ORDER:
                qual_entry.get('merkmale', []).append({
                    'merkmal': merkmal_name_to_update,
                    'wert': new_status_value,
                    'start_date': new_start_date,
                    'end_date': new_end_date,
                    'cert_path': new_cert_path
                })
                updated_in_memory = True
            break

    if not found_employee:
        return jsonify({"error": f"Mitarbeiter mit ID '{user_id}' nicht gefunden."}), 404

    if not updated_in_memory:
        return jsonify(
            {"error": f"Merkmal '{merkmal_name_to_update}' konnte für den Mitarbeiter nicht aktualisiert werden."}), 400

    if save_qualifications_to_csv(QUALIFICATIONS_FILE_PATH, ALL_QUALIFICATIONS, MERKMALE_IN_ORDER, silent=True):
        return jsonify({"message": "Qualifikationsdetails erfolgreich aktualisiert."}), 200
    else:
        return jsonify({"error": "Fehler beim Speichern der Qualifikationsdatei."}), 500


@app.route('/api/get_merkmale', methods=['GET'])
@login_required
def api_get_merkmale():
    global MERKMALE_IN_ORDER
    if not MERKMALE_IN_ORDER and os.path.exists(QUALIFICATIONS_FILE_PATH):
        _, loaded_merkmale = load_qualifications_from_csv(QUALIFICATIONS_FILE_PATH)
        if loaded_merkmale: MERKMALE_IN_ORDER = loaded_merkmale

    return jsonify(MERKMALE_IN_ORDER)


@app.route('/api/update_merkmale', methods=['POST'])
@admin_required
def api_update_merkmale():
    data = request.json
    if not data or 'merkmale' not in data or not isinstance(data['merkmale'], list):
        return jsonify({"error": "Ungültige Daten für Merkmale."}), 400

    new_merkmale_order_from_client = [str(m).strip() for m in data['merkmale'] if str(m).strip()]

    global ALL_QUALIFICATIONS, MERKMALE_IN_ORDER

    # Stelle sicher, dass die Daten geladen sind
    if not ALL_QUALIFICATIONS or not MERKMALE_IN_ORDER:
        load_and_prepare_qualifications_data()

    # Speichere den alten Zustand für ein mögliches Rollback
    original_merkmale_in_order = list(MERKMALE_IN_ORDER)
    # Eine tiefe Kopie der Qualifikationsdaten für ein Rollback bei Fehler
    import copy
    original_qualifications = copy.deepcopy(ALL_QUALIFICATIONS)

    # Finde heraus, welche Merkmale hinzugefügt oder entfernt wurden
    set_original_merkmale = set(original_merkmale_in_order)
    set_new_merkmale = set(new_merkmale_order_from_client)

    added_merkmale = list(set_new_merkmale - set_original_merkmale)
    removed_merkmale = list(set_original_merkmale - set_new_merkmale)

    # Aktualisiere die In-Memory-Daten für jeden Mitarbeiter
    for qual_entry in ALL_QUALIFICATIONS:
        # 1. Entferne Daten für gelöschte Merkmale
        if removed_merkmale:
            qual_entry['merkmale'] = [m for m in qual_entry.get('merkmale', []) if
                                      m.get('merkmal') not in removed_merkmale]

        # 2. Füge leere Datensätze für neue Merkmale hinzu
        for added_m_name in added_merkmale:
            # Stelle sicher, dass das Merkmal nicht schon (durch einen Fehler) existiert
            if not any(m.get('merkmal') == added_m_name for m in qual_entry.get('merkmale', [])):
                qual_entry.get('merkmale', []).append({
                    'merkmal': added_m_name,
                    'wert': '',
                    'start_date': '',
                    'end_date': '',
                    'cert_path': ''
                })

    MERKMALE_IN_ORDER = new_merkmale_order_from_client

    # Versuche, die Änderungen in die CSV zu schreiben
    if save_qualifications_to_csv(QUALIFICATIONS_FILE_PATH, ALL_QUALIFICATIONS, MERKMALE_IN_ORDER, silent=False):
        return jsonify({"message": "Merkmale erfolgreich aktualisiert und CSV gespeichert."}), 200
    else:
        MERKMALE_IN_ORDER = original_merkmale_in_order
        ALL_QUALIFICATIONS = original_qualifications
        print(
            "FEHLER: Änderungen an Merkmalen konnten nicht gespeichert werden. Rollback der In-Memory-Daten wurde durchgeführt.")
        return jsonify({"error": "Fehler beim Speichern der aktualisierten Qualifikations-CSV."}), 500


@app.route('/api/admin/global_sap_update', methods=['POST'])
@admin_required
def api_global_sap_update():
    if 'sap_file' not in request.files:
        return jsonify({"error": "Keine Datei hochgeladen."}), 400

    file = request.files['sap_file']
    if not file.filename.lower().endswith('.csv'):
        return jsonify({"error": "Ungültiges Dateiformat. Bitte CSV verwenden."}), 400

    try:
        # 1. Datei EINMALIG in den Speicher laden und vorverarbeiten (Performance!)
        import pandas as pd

        file_stream = io.BytesIO(file.read())
        try:
            # Grundeinstellungen für SAP Export lesen
            df_master = pd.read_csv(file_stream, sep=';', encoding='utf-8', dayfirst=True, low_memory=False)
        except Exception as e:
            return jsonify({"error": f"Konnte CSV nicht lesen: {str(e)}"}), 400

        # Prüfen ob notwendige Spalten da sind
        required_cols = ["Project", "Engine Serial", "Customer", "Engine Type", "Milestone type", "TS"]
        if not all(col in df_master.columns for col in required_cols):
            return jsonify({"error": "CSV hat nicht das erwartete SAP-Format (Spalten fehlen)."}), 400

        # 2. Globale Vorfilterung (Datum & Meilenstein-Typ) - gilt für ALLE Boards
        df_master['TS_parsed'] = pd.to_datetime(df_master['TS'], format='%d.%m.%Y %H:%M:%S', errors='coerce')
        today = datetime.now()
        # Zeitraum: 6 Wochen zurück bis 6 Wochen in Zukunft (wie in der Original-Funktion)
        start_date, end_date = today - timedelta(weeks=4), today + timedelta(weeks=6)

        # Filtern auf Zeitraum und "Plan"-Events
        df_master = df_master.dropna(subset=['TS_parsed'])
        df_master = df_master.loc[(df_master['TS_parsed'] >= start_date) & (df_master['TS_parsed'] <= end_date)]
        df_master = df_master[df_master['Milestone type'].astype(str) == "Plan (Planung, ZMMP)"]

        if df_master.empty:
            return jsonify({"message": "Keine relevanten 'Plan'-Meilensteine im Zeitraum +/- 6 Wochen gefunden."}), 200

        # 3. Iteration durch alle Timelines
        config = load_app_config()
        timelines = config.get('timelines', [])
        results = []

        for tl in timelines:
            if tl.get('type') != 'editable' or not tl.get('sap_filter') or not tl.get('data_file'):
                continue

            target_file = tl['data_file']
            sap_filters = tl['sap_filter']  # z.B. {"Engine Type": ["CFM56"]}

            # 4. Spezifische Filterung für dieses Board
            df_timeline = df_master.copy()

            filter_match = True
            for col, patterns in sap_filters.items():
                if col in df_timeline.columns and patterns:
                    try:
                        combined_pattern = '|'.join(patterns)
                        df_timeline = df_timeline[
                            df_timeline[col].astype(str).str.contains(combined_pattern, regex=True, na=False,
                                                                      case=False)]
                    except:
                        filter_match = False
                        break

            if not filter_match or df_timeline.empty:
                results.append(f"Skipped: {tl['name']} (Keine Daten nach Filter)")
                continue

            # 5. Merge durchführen
            # Wir müssen die aktuelle Projekt-Reihenfolge laden, um sie nicht zu zerstören
            current_projects = load_timeline_projects_for_flask([target_file])
            current_order = [(p.get('project_code', ''), p.get('engine_serial', '')) for p in current_projects]

            changes = merge_into_timeline_projects(target_file, df_timeline, current_order)

            status = "Aktualisiert" if changes else "Keine Änderungen"
            results.append(f"<strong>{tl['name']}</strong>: {status}")

        return jsonify({
            "success": True,
            "message": "Globales Update abgeschlossen.",
            "details": "<br>".join(results)
        }), 200

    except Exception as e:
        traceback.print_exc()
        return jsonify({"error": f"Systemfehler beim globalen Update: {str(e)}"}), 500


if __name__ == '__main__':
    # 1. Benutzer laden (ist Voraussetzung für alles andere)
    if not os.path.exists(USERS_FILE_PATH):
        print(f"WARNUNG: Benutzerdatei '{USERS_FILE_PATH}' nicht gefunden. Erstelle Beispieldatei.")
        try:
            with open(USERS_FILE_PATH, 'w', newline='', encoding='utf-8-sig') as f_user:
                writer_user = csv.writer(f_user)
                writer_user.writerow(USER_CSV_FIELDNAMES)
                writer_user.writerow(['Admin', 'Local', 'admin', 'Teamleiter', 'MBET', 'x', 'x', 'x'])
                writer_user.writerow(['User', 'Normal', 'user', 'Mechaniker', 'MBET', '', 'x', ''])
            print(f"INFO: Beispieldatei '{USERS_FILE_PATH}' wurde erstellt.")
        except Exception as e_create_user:
            print(f"FEHLER beim Erstellen der Benutzer-Beispieldatei: {e_create_user}")
    load_all_users_on_startup()
    migrate_project_status_files()
    migrate_completeness_log()
    validate_group_references()

    if not os.path.exists(HARDWARE_UPLOADS_DIR):
        os.makedirs(HARDWARE_UPLOADS_DIR)

    # 2. Testzellen und deren manuelle Events laden (NEUE LOGIK)
    # 2a. Stammdaten der Testzellen prüfen
    init_facility_data()
    init_maintenance_structure()
    if not os.path.exists(LOCATIONS_FILE_PATH):
        print(f"WARNUNG: Testzellen-Stammdatendatei '{LOCATIONS_FILE_PATH}' nicht gefunden. Erstelle Beispieldatei.")
        try:
            with open(LOCATIONS_FILE_PATH, 'w', newline='', encoding='utf-8-sig') as f_loc:
                writer = csv.writer(f_loc)
                writer.writerow(['ID', 'TESTCELL', 'TYPES'])
                writer.writerow(['1', 'Prüfstand 4.1', 'CFM56,LEAP-1A'])
                writer.writerow(['2', 'Prüfstand 4.2', 'PW1500,PW1900'])
            print(f"INFO: Beispieldatei '{LOCATIONS_FILE_PATH}' wurde erstellt.")
        except Exception as e:
            print(f"FEHLER beim Erstellen der Testzellen-Beispieldatei: {e}")

    # 2b. Manuelle Events der Testzellen prüfen
    if not os.path.exists(LOCATIONS_EVENTS_CSV_PATH):
        print(f"WARNUNG: Testzellen-Eventdatei '{LOCATIONS_EVENTS_CSV_PATH}' nicht gefunden. Erstelle leere Datei.")
        try:
            with open(LOCATIONS_EVENTS_CSV_PATH, 'w', newline='', encoding='utf-8-sig') as f_evt:
                writer = csv.writer(f_evt)
                writer.writerow(
                    ['event_id', 'cell_id', 'title', 'type', 'start_date', 'start_shift', 'end_date', 'end_shift',
                     'description'])
            print(f"INFO: Leere Eventdatei '{LOCATIONS_EVENTS_CSV_PATH}' wurde erstellt.")
        except Exception as e:
            print(f"FEHLER beim Erstellen der Testzellen-Eventdatei: {e}")

    # 2c. Daten laden (ersetzt den alten load_locations() Aufruf)
    load_locations_and_events()

    # 2d. Anwesenheits-Verzeichnis initialisieren
    os.makedirs(ATTENDANCE_DIR, exist_ok=True)

    # 3. Mitarbeiter-Anwesenheiten laden (wird für Sync benötigt)
    if not os.path.exists(EMPLOYEES_FILE_PATH):
        print(f"WARNUNG: Mitarbeiterdatei '{EMPLOYEES_FILE_PATH}' nicht gefunden. Erstelle Beispieldatei.")
        try:
            with open(EMPLOYEES_FILE_PATH, 'w', newline='', encoding='utf-8-sig') as f_emp:
                writer_emp = csv.writer(f_emp)
                emp_header = [" ", "Name", "ID"]
                kw_today = date.today().isocalendar()[1]
                year_short_today = str(date.today().year)[-2:]
                emp_header.append(f"KW{kw_today}_{year_short_today}")
                for i in range(7):
                    day_header_emp = date.today() - timedelta(days=date.today().weekday()) + timedelta(days=i)
                    emp_header.append(
                        f"{['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'][day_header_emp.weekday()]}\\n{day_header_emp.strftime('%d.%m.')}")
                writer_emp.writerow(emp_header)
                writer_emp.writerow(['FF', 'Mustermann, Max', 'mmuster'] + ['F'] + [''] * 7)
            print(f"INFO: Beispieldatei '{EMPLOYEES_FILE_PATH}' wurde erstellt.")
        except Exception as e_create_e:
            print(f"FEHLER beim Erstellen der Mitarbeiter-Beispieldatei: {e_create_e}")

    # 4. Qualifikationen laden und mit Mitarbeitern synchronisieren
    if not os.path.exists(QUALIFICATIONS_FILE_PATH):
        print(
            f"FEHLER: Die Qualifikationsdatei '{QUALIFICATIONS_FILE_PATH}' wurde nicht gefunden. Die Anwendung kann ohne diese Datei nicht korrekt funktionieren.")
    load_and_prepare_qualifications_data()

    # 5. Projekte laden
    if not os.path.exists(CURRENT_PROJECT_FILE_PATH):
        print(f"WARNUNG: Projektdatei '{CURRENT_PROJECT_FILE_PATH}' nicht gefunden. Erstelle leere Datei.")
        try:
            with open(CURRENT_PROJECT_FILE_PATH, 'w', newline='', encoding='utf-8-sig') as f_proj:
                writer = csv.writer(f_proj)
                # Header für eine leere Projektdatei
                writer.writerow(
                    ['name', 'engine_type', 'project_code', 'engine_serial', 'customer', 'BE', 'TS', 'TE', 'SA',
                     'active'])
            print(f"INFO: Leere Projektdatei '{CURRENT_PROJECT_FILE_PATH}' mit Header wurde erstellt.")
        except Exception as e_create_proj:
            print(f"FEHLER beim Erstellen der Projekt-Datei: {e_create_proj}")

    # 6. Kommentare laden (erstellt Datei, falls nicht vorhanden)
    load_all_comments()

    # 7. Analytics-Flush-Thread starten + bestehende Counts laden
    os.makedirs(ANALYTICS_DIR, exist_ok=True)
    if os.path.exists(ANALYTICS_FILE):
        try:
            with open(ANALYTICS_FILE, 'r', encoding='utf-8') as f:
                saved = json.load(f)
            page_counter_total.update(saved.get('total', {}))
            for week, counts in saved.get('by_week', {}).items():
                page_counter_weekly[week].update(counts)
            for week, visitors in saved.get('unique_visitors_by_week', {}).items():
                weekly_active_users[week].update(visitors)
            for week, counts in saved.get('user_traffic', {}).get('by_week', {}).items():
                user_traffic_weekly[week].update(counts)
            user_traffic_total.update(saved.get('user_traffic', {}).get('total', {}))
            print(f"INFO: Analytics-Daten geladen ({sum(page_counter_total.values())} Aufrufe gesamt, {len(page_counter_weekly)} Wochen, {len(weekly_active_users)} Wochen mit Visitor-Daten).")
        except Exception as e:
            print(f"WARNUNG: Analytics-Datei konnte nicht geladen werden: {e}")
    threading.Thread(target=_flush_analytics, daemon=True, name='analytics-flush').start()
    print("INFO: Analytics-Tracking aktiv. Daten werden alle 5 Min. nach db/analytics/page_views.json geschrieben.")

    # 8. App starten
    try:
        app.run(debug=True, host='0.0.0.0', port=5099, use_reloader=False)
    except Exception as e:
        print(f"Kritischer Fehler beim App-Start: {e}")
        traceback.print_exc()